Aller au contenu principal

Créer une boutique e-commerce performante avec Next.js en Belgique

Guide complet pour lancer votre boutique en ligne avec Next.js : paiements Stripe, gestion catalogue, SEO e-commerce et conformité RGPD. Solution moderne pour PME belges.

Créer une boutique e-commerce performante avec Next.js en Belgique

Le e-commerce belge a généré **14,2 milliards d'euros** en 2024, avec une croissance annuelle de 12%. Les consommateurs privilégient désormais les sites rapides, sécurisés et mobile-first. Next.js s'impose comme la solution technique idéale pour créer une boutique en ligne moderne qui convertit.

Pourquoi Next.js pour l'e-commerce ?

Les plateformes e-commerce traditionnelles (WooCommerce, PrestaShop, Magento) montrent leurs limites face aux exigences actuelles. Next.js offre une alternative moderne avec des avantages décisifs.

Comparaison avec les plateformes classiques

Critère Next.js WooCommerce Shopify PrestaShop
Performance Lighthouse 95-100 40-60 60-75 45-65
Temps de chargement < 1s 3-5s 2-3s 3-6s
Évolutivité Illimitée Limitée serveur Limitée plan Limitée serveur
Coût mensuel 0-50€ 20-200€ 29-299€ 50-300€
Personnalisation Totale Thèmes/Plugins Limitée Modules
SEO natif Excellent Bon Moyen Bon
Mobile-first Natif Variable Bon Variable

Avantages business pour PME belges

**1. Performance = Conversion**

  • Chaque seconde gagnée augmente la conversion de 7%
  • Un site à 100/100 Lighthouse convertit 2,5× mieux qu'un site à 50/100
  • Google favorise les sites rapides dans les résultats

**2. Coûts maîtrisés**

  • Pas de licence mensuelle (Shopify, BigCommerce)
  • Hébergement économique (Vercel, Netlify gratuit jusqu'à 100k vues/mois)
  • Pas de commission sur les ventes (contrairement à Shopify/Wix)

**3. Contrôle total**

  • Propriétaire à 100% de votre code et données
  • Intégration native avec vos outils existants
  • Évolutions illimitées sans contraintes de plateforme

Architecture technique recommandée

Voici la stack technique optimale pour une boutique Next.js en Belgique.

Stack moderne e-commerce

// Configuration Next.js pour e-commerce
// next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'res.cloudinary.com', // CDN images produits
      },
    ],
    formats: ['image/avif', 'image/webp'],
  },
  
  // Optimisation e-commerce
  experimental: {
    optimizePackageImports: ['lucide-react', '@stripe/stripe-js'],
  },
  
  // Headers sécurité pour paiements
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "frame-ancestors 'none'; default-src 'self'; script-src 'self' 'unsafe-inline' https://js.stripe.com; frame-src https://js.stripe.com;"
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload'
          },
        ],
      },
    ];
  },
};

export default config;

Components essentiels

**Base de données : PostgreSQL + Prisma**

// prisma/schema.prisma
model Product {
  id          String   @id @default(cuid())
  name        String
  slug        String   @unique
  description String
  price       Float
  stock       Int
  images      String[] // URLs Cloudinary
  category    Category @relation(fields: [categoryId], references: [id])
  categoryId  String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  
  @@index([slug])
  @@index([categoryId])
}

model Category {
  id       String    @id @default(cuid())
  name     String
  slug     String    @unique
  products Product[]
}

model Order {
  id              String      @id @default(cuid())
  orderNumber     String      @unique
  customerEmail   String
  customerName    String
  shippingAddress Json
  items           OrderItem[]
  subtotal        Float
  shipping        Float
  tax             Float
  total           Float
  status          OrderStatus @default(PENDING)
  stripePaymentId String?     @unique
  createdAt       DateTime    @default(now())
  
  @@index([customerEmail])
  @@index([status])
}

model OrderItem {
  id        String  @id @default(cuid())
  order     Order   @relation(fields: [orderId], references: [id])
  orderId   String
  productId String
  name      String
  price     Float
  quantity  Int
}

enum OrderStatus {
  PENDING
  PAID
  PROCESSING
  SHIPPED
  DELIVERED
  CANCELLED
}

Intégration paiement Stripe

Stripe est le processeur de paiement le plus utilisé en Belgique pour sa simplicité d'intégration et sa conformité PSD2.

Configuration Stripe

npm install @stripe/stripe-js stripe
// lib/stripe.ts
import Stripe from 'stripe';
import { loadStripe } from '@stripe/stripe-js';

// Côté serveur
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
  typescript: true,
});

// Côté client
let stripePromise: Promise<any>;
export const getStripe = () => {
  if (!stripePromise) {
    stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
  }
  return stripePromise;
};

Route API Checkout

// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { prisma } from '@/lib/prisma';
import Stripe from 'stripe';

export async function POST(request: NextRequest) {
  try {
    const { items, customerEmail, shippingAddress } = await request.json();

    // Validation
    if (!items || items.length === 0) {
      return NextResponse.json(
        { error: 'Panier vide' },
        { status: 400 }
      );
    }

    // Calcul des montants
    const subtotal = items.reduce((acc: number, item: any) => 
      acc + (item.price * item.quantity), 0
    );
    const shipping = subtotal > 50 ? 0 : 5.99; // Livraison gratuite > 50€
    const tax = (subtotal + shipping) * 0.21; // TVA belge 21%
    const total = subtotal + shipping + tax;

    // Créer la commande
    const order = await prisma.order.create({
      data: {
        orderNumber: `CMD-${Date.now()}`,
        customerEmail,
        customerName: shippingAddress.name,
        shippingAddress,
        subtotal,
        shipping,
        tax,
        total,
        status: 'PENDING',
        items: {
          create: items.map((item: any) => ({
            productId: item.id,
            name: item.name,
            price: item.price,
            quantity: item.quantity,
          })),
        },
      },
    });

    // Créer session Stripe Checkout
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card', 'bancontact'], // Méthodes BE
      line_items: items.map((item: any) => ({
        price_data: {
          currency: 'eur',
          product_data: {
            name: item.name,
            images: [item.image],
          },
          unit_amount: Math.round(item.price * 100), // Centimes
        },
        quantity: item.quantity,
      })),
      mode: 'payment',
      success_url: `${process.env.NEXT_PUBLIC_URL}/commande/confirmation?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_URL}/panier`,
      customer_email: customerEmail,
      metadata: {
        orderId: order.id,
      },
      shipping_address_collection: {
        allowed_countries: ['BE', 'FR', 'NL', 'LU', 'DE'], // Benelux + voisins
      },
    });

    return NextResponse.json({ sessionId: session.id });
  } catch (error) {
    console.error('Erreur checkout:', error);
    return NextResponse.json(
      { error: 'Erreur lors de la création du paiement' },
      { status: 500 }
    );
  }
}

Webhook Stripe pour confirmation

// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { prisma } from '@/lib/prisma';
import Stripe from 'stripe';

export async function POST(request: NextRequest) {
  const body = await request.text();
  const signature = request.headers.get('stripe-signature')!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json(
      { error: 'Signature invalide' },
      { status: 400 }
    );
  }

  // Gérer les événements
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      const orderId = session.metadata?.orderId;

      if (orderId) {
        // Mettre à jour la commande
        await prisma.order.update({
          where: { id: orderId },
          data: {
            status: 'PAID',
            stripePaymentId: session.payment_intent as string,
          },
        });

        // TODO: Envoyer email de confirmation
        // TODO: Décrémenter le stock
        // TODO: Notifier l'équipe
      }
      break;
    }

    case 'payment_intent.payment_failed': {
      const paymentIntent = event.data.object as Stripe.PaymentIntent;
      // TODO: Gérer l'échec
      break;
    }
  }

  return NextResponse.json({ received: true });
}

Gestion du catalogue produits

Un catalogue bien structuré améliore l'expérience utilisateur et le SEO.

Page produit optimisée SEO

// app/produits/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { Metadata } from 'next';
import { prisma } from '@/lib/prisma';
import Image from 'next/image';
import { AddToCartButton } from '@/components/shop/AddToCartButton';
import { ProductSchema } from '@/components/seo/ProductSchema';

interface Props {
  params: { slug: string };
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const product = await prisma.product.findUnique({
    where: { slug: params.slug },
    include: { category: true },
  });

  if (!product) return {};

  return {
    title: `${product.name} | Boutique Smidjan`,
    description: product.description.substring(0, 160),
    alternates: {
      canonical: `/produits/${product.slug}`,
    },
    openGraph: {
      title: product.name,
      description: product.description,
      images: [{ url: product.images[0], width: 1200, height: 630 }],
      type: 'product',
    },
  };
}

export default async function ProductPage({ params }: Props) {
  const product = await prisma.product.findUnique({
    where: { slug: params.slug },
    include: { category: true },
  });

  if (!product) notFound();

  const inStock = product.stock > 0;

  return (
    <>
      <ProductSchema product={product} />
      
      <div className="product-page">
        <div className="product-gallery">
          {product.images.map((image, i) => (
            <Image
              key={i}
              src={image}
              alt={`${product.name} - vue ${i + 1}`}
              width={800}
              height={800}
              priority={i === 0}
              quality={90}
            />
          ))}
        </div>

        <div className="product-details">
          <h1>{product.name}</h1>
          <p className="category">{product.category.name}</p>
          
          <div className="price">
            {product.price.toFixed(2)}€
            <span className="tax-info">TVA incluse</span>
          </div>

          <p className="description">{product.description}</p>

          {inStock ? (
            <>
              <p className="stock">En stock ({product.stock} disponibles)</p>
              <AddToCartButton product={product} />
            </>
          ) : (
            <p className="out-of-stock">Rupture de stock</p>
          )}

          <div className="shipping-info">
            <p>✓ Livraison gratuite dès 50€</p>
            <p>✓ Expédition sous 24-48h</p>
            <p>✓ Retour gratuit sous 14 jours</p>
          </div>
        </div>
      </div>
    </>
  );
}

// Génération statique des produits populaires
export async function generateStaticParams() {
  const products = await prisma.product.findMany({
    select: { slug: true },
    take: 20, // Top 20 produits
  });

  return products.map((product) => ({
    slug: product.slug,
  }));
}

Schema.org Product

// components/seo/ProductSchema.tsx
export function ProductSchema({ product }: { product: any }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    image: product.images,
    sku: product.id,
    brand: {
      '@type': 'Brand',
      name: 'Smidjan',
    },
    offers: {
      '@type': 'Offer',
      url: `https://smidjan.be/produits/${product.slug}`,
      priceCurrency: 'EUR',
      price: product.price,
      availability: product.stock > 0 
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock',
      seller: {
        '@type': 'Organization',
        name: 'Smidjan',
      },
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

SEO e-commerce : techniques avancées

Le SEO e-commerce diffère du SEO éditorial par son orientation transactionnelle.

Optimisations on-page essentielles

**1. URLs descriptives**

✓ /produits/chaise-bureau-ergonomique-noir
✗ /produit?id=12345

**2. Titres optimisés**

<!-- Formule gagnante -->
<title>Nom Produit | Catégorie | Prix - Marque</title>

<!-- Exemple -->
<title>Chaise Bureau Ergonomique Noire | Mobilier | 249€ - Smidjan</title>

**3. Descriptions uniques par produit** Évitez les descriptions génériques fournies par les fabricants. Rédigez des descriptions originales intégrant :

  • Bénéfices client (pas seulement features)
  • Mots-clés long-tail naturels
  • Cas d'usage concrets
  • Informations techniques

**4. Images optimisées**

<Image
  src="/products/chaise-bureau.webp"
  alt="Chaise de bureau ergonomique noire avec support lombaire réglable"
  width={800}
  height={800}
  // Alt descriptif pour SEO + accessibilité
/>

Stratégie contenu e-commerce

**Pages catégories enrichies**

Ajoutez 300-500 mots de contenu unique en haut de chaque page catégorie :

<div className="category-intro">
  <h1>Mobilier de Bureau Ergonomique en Belgique</h1>
  <p>
    Découvrez notre sélection de mobilier de bureau conçu pour améliorer 
    votre confort et productivité. Livraison rapide en Wallonie et Bruxelles.
  </p>
  <p>
    Que vous équipiez un bureau à domicile à Liège ou des locaux professionnels 
    à Namur, nos solutions ergonomiques s'adaptent à vos besoins...
  </p>
</div>

**Blog intégré pour trafic SEO**

Créez du contenu informatif lié à vos produits :

  • "Comment choisir sa chaise de bureau en 2025"
  • "Top 10 accessoires pour télétravail efficace"
  • "Ergonomie au bureau : guide complet"

Chaque article doit lier vers 3-5 produits pertinents.

Conformité RGPD et e-commerce

Les boutiques en ligne belges doivent respecter des obligations strictes.

Pages légales obligatoires

**1. Conditions générales de vente (CGV)**

  • Identité du vendeur (numéro BCE)
  • Caractéristiques essentielles des produits
  • Prix TTC et frais de livraison
  • Modalités de paiement
  • Droit de rétractation (14 jours)
  • Garanties légales

**2. Politique de confidentialité**

  • Données collectées et finalités
  • Base légale (contrat, consentement)
  • Durée de conservation
  • Droits RGPD (accès, rectification, suppression)
  • Coordonnées DPO si applicable

**3. Mentions légales**

  • Dénomination sociale
  • Siège social
  • Numéro BCE
  • Numéro TVA
  • Email et téléphone
  • Hébergeur du site

Consentement cookies e-commerce

// components/CookieConsent.tsx
'use client';

import { useState, useEffect } from 'react';

export function CookieConsent() {
  const [show, setShow] = useState(false);

  useEffect(() => {
    const consent = localStorage.getItem('cookie-consent');
    if (!consent) setShow(true);
  }, []);

  const accept = (type: 'all' | 'essential') => {
    localStorage.setItem('cookie-consent', type);
    
    if (type === 'all') {
      // Activer Google Analytics, Meta Pixel, etc.
      window.gtag?.('consent', 'update', {
        analytics_storage: 'granted',
        ad_storage: 'granted',
      });
    }
    
    setShow(false);
  };

  if (!show) return null;

  return (
    <div className="cookie-banner">
      <div className="cookie-content">
        <h3>Cookies et confidentialité</h3>
        <p>
          Nous utilisons des cookies essentiels pour le fonctionnement du panier 
          et des cookies analytiques pour améliorer votre expérience.
        </p>
        <div className="cookie-buttons">
          <button onClick={() => accept('essential')}>
            Essentiels uniquement
          </button>
          <button onClick={() => accept('all')} className="primary">
            Accepter tout
          </button>
        </div>
        <a href="/politique-confidentialite">En savoir plus</a>
      </div>
    </div>
  );
}

Checklist de lancement

Avant de mettre en ligne votre boutique Next.js :

Technique

  • ✓ HTTPS configuré avec certificat valide
  • ✓ Stripe en mode production (clés live)
  • ✓ Webhook Stripe testé en production
  • ✓ Base de données backupée quotidiennement
  • ✓ Variables d'environnement sécurisées
  • ✓ Monitoring d'erreurs activé (Sentry)
  • ✓ Score Lighthouse > 90 sur toutes les pages clés

E-commerce

  • ✓ Catalogue complet avec images HD
  • ✓ Descriptions uniques pour chaque produit
  • ✓ Prix TTC affichés (TVA 21% Belgique)
  • ✓ Frais de livraison calculés correctement
  • ✓ Processus de commande testé de bout en bout
  • ✓ Emails transactionnels configurés
  • ✓ Système de gestion des stocks fonctionnel

Légal

  • ✓ CGV rédigées et accessibles
  • ✓ Politique de confidentialité RGPD-compliant
  • ✓ Mentions légales complètes
  • ✓ Droit de rétractation expliqué
  • ✓ Banner cookies avec consentement
  • ✓ Numéro BCE et TVA affichés

SEO

  • ✓ Sitemap.xml avec tous les produits
  • ✓ Robots.txt configuré
  • ✓ Schema.org Product sur chaque fiche
  • ✓ OpenGraph pour partage social
  • ✓ Google Search Console configurée
  • ✓ Google Analytics ou alternative installée

Résultats attendus

Avec une boutique Next.js bien optimisée, vous pouvez espérer :

**Performance**

  • Score Lighthouse 95-100/100
  • Temps de chargement < 1,5s
  • Taux de rebond réduit de 40%

**Conversion**

  • Taux de conversion 2-4% (vs 1-2% plateformes classiques)
  • Panier moyen +15% grâce à la vitesse
  • Taux d'abandon panier < 60%

**SEO**

  • Indexation rapide (< 48h)
  • Positionnement local amélioré
  • Trafic organique croissant de 20-30% par trimestre

**Prêt à lancer votre boutique ?** Smidjan développe des solutions e-commerce Next.js sur mesure pour PME belges. Audit gratuit et devis personnalisé.

Besoin d'accompagnement ?

Smidjan vous aide à mettre en place ces solutions pour votre entreprise en Belgique.

Discutons de votre projet →