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 →