J'ai Reconstruit Mon Premier Site de 2015 avec les Outils de 2025
10 ans d'évolution web en un projet. jQuery vs React, FTP vs Vercel, tables vs Flexbox. Voyage dans le temps du développement web.

title: "J'ai Reconstruit Mon Premier Site de 2015 avec les Outils de 2025" description: "10 ans d'évolution web en un projet. jQuery vs React, FTP vs Vercel, tables vs Flexbox. Voyage dans le temps du développement web." date: "2025-12-11" author: name: "Mustapha Hamadi" role: "Développeur Full-Stack" image: "/avatar.jpg" tags: ["Évolution", "Nostalgie", "Technique"] category: "development" image: "/blog/site-2015-vs-2025-reconstruction-hero.png" ogImage: "/blog/site-2015-vs-2025-reconstruction-hero.png" featured: true published: true keywords: ["évolution web", "développement 2015 vs 2025", "jQuery vs React", "histoire web", "outils développement", "progression développeur", "avant après code", "modernisation site", "refonte technique", "nostalgie développeur", "technologies web", "transformation digitale"]
J'ai Reconstruit Mon Premier Site de 2015 avec les Outils de 2025
J'ai retrouvé les fichiers de mon premier "vrai" projet client. Un site pour un restaurant local, livré en septembre 2015. En le rouvrant, j'ai ressenti un mélange de nostalgie et... de gêne.
Plutôt que de simplement rire de mon ancien code, j'ai décidé de faire une expérience : reconstruire exactement le même site avec les outils de 2025. Mêmes fonctionnalités, même design, nouvelle stack.
Voici ce voyage de 10 ans de développement web condensé en un projet.
Le Site Original (2015)
La Stack de l'Époque
Structure du projet 2015 :
restaurant-site/
├── index.html
├── menu.html
├── contact.html
├── galerie.html
├── css/
│ ├── style.css
│ ├── bootstrap.min.css (v3.3.5)
│ └── font-awesome.min.css
├── js/
│ ├── jquery-1.11.3.min.js
│ ├── bootstrap.min.js
│ ├── main.js
│ └── jquery.validate.min.js
├── images/
│ └── (photos JPEG non optimisées, 2-4 MB chacune)
└── php/
└── contact.php
Le Code HTML (Avant)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Restaurant Le Bon Goût - Cuisine traditionnelle</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="index.html">Le Bon Goût</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="active"><a href="index.html">Accueil</a></li>
<li><a href="menu.html">Menu</a></li>
<li><a href="galerie.html">Galerie</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</div>
</div>
</nav>
<!-- Hero avec carousel Bootstrap -->
<div id="carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carousel" data-slide-to="0" class="active"></li>
<li data-target="#carousel" data-slide-to="1"></li>
</ol>
<div class="carousel-inner">
<div class="item active">
<img src="images/hero1.jpg" alt="Restaurant">
<div class="carousel-caption">
<h1>Bienvenue au Bon Goût</h1>
</div>
</div>
</div>
</div>
<!-- Scripts en bas de page (mais bloquants quand même) -->
<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Le JavaScript (Avant)
// main.js - Le JavaScript de 2015
$(document).ready(function() {
// Smooth scroll (fallback maison)
$('a[href*="#"]').on('click', function(e) {
e.preventDefault();
var target = $(this.hash);
$('html, body').animate({
scrollTop: target.offset().top - 70
}, 800);
});
// Animation au scroll (sans librairie)
$(window).scroll(function() {
$('.fade-in').each(function() {
var top = $(this).offset().top;
var bottom = top + $(this).outerHeight();
var windowTop = $(window).scrollTop();
var windowBottom = windowTop + $(window).height();
if (windowBottom > top && windowTop < bottom) {
$(this).addClass('visible');
}
});
});
// Validation formulaire
$('#contactForm').validate({
rules: {
name: { required: true, minlength: 2 },
email: { required: true, email: true },
message: { required: true, minlength: 10 }
},
messages: {
name: "Veuillez entrer votre nom",
email: "Veuillez entrer un email valide",
message: "Votre message doit faire au moins 10 caractères"
},
submitHandler: function(form) {
$.ajax({
type: 'POST',
url: 'php/contact.php',
data: $(form).serialize(),
success: function() {
alert('Message envoyé !');
$(form)[0].reset();
},
error: function() {
alert('Erreur, veuillez réessayer.');
}
});
}
});
// Galerie Lightbox maison
$('.galerie img').click(function() {
var src = $(this).attr('src');
$('body').append('<div class="lightbox"><img src="' + src + '"><span class="close">×</span></div>');
$('.lightbox').fadeIn();
});
$(document).on('click', '.lightbox .close, .lightbox', function() {
$('.lightbox').fadeOut(function() {
$(this).remove();
});
});
});
Le CSS (Avant)
/* style.css - CSS de 2015 */
/* Reset basique */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Typography */
body {
font-family: 'Open Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
color: #333;
}
/* Navigation fixe Bootstrap override */
.navbar-default {
background-color: #fff;
border-bottom: 1px solid #eee;
-webkit-box-shadow: 0 2px 5px rgba(0,0,0,0.1);
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
/* Hero section */
.carousel {
margin-top: 50px;
}
.carousel-caption h1 {
font-size: 48px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
/* Cards menu - avec floats (pas de flexbox en 2015) */
.menu-section {
overflow: hidden; /* clearfix */
}
.menu-item {
float: left;
width: 33.33%;
padding: 15px;
}
/* Clearfix hack */
.menu-section:after {
content: "";
display: table;
clear: both;
}
/* Responsive avec media queries manuelles */
@media (max-width: 992px) {
.menu-item {
width: 50%;
}
}
@media (max-width: 768px) {
.menu-item {
width: 100%;
float: none;
}
.carousel-caption h1 {
font-size: 24px;
}
}
/* Animations manuelles */
.fade-in {
opacity: 0;
-webkit-transform: translateY(20px);
transform: translateY(20px);
-webkit-transition: opacity 0.6s, transform 0.6s;
transition: opacity 0.6s, transform 0.6s;
}
.fade-in.visible {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
Le Déploiement (2015)
# Déploiement via FileZilla (FTP)
# 1. Ouvrir FileZilla
# 2. Se connecter à l'hébergeur OVH
# 3. Naviguer vers /www/
# 4. Glisser-déposer tous les fichiers
# 5. Attendre 10 minutes
# 6. Prier pour que ça marche
# 7. Réaliser qu'on a oublié de modifier le chemin de la BDD
# 8. Recommencer
Le Site Reconstruit (2025)
La Nouvelle Stack
Structure du projet 2025 :
restaurant-site-2025/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── menu/
│ │ └── page.tsx
│ ├── galerie/
│ │ └── page.tsx
│ └── contact/
│ └── page.tsx
├── components/
│ ├── Header.tsx
│ ├── Hero.tsx
│ ├── MenuCard.tsx
│ ├── Gallery.tsx
│ └── ContactForm.tsx
├── lib/
│ └── actions.ts
├── public/
│ └── images/ (optimisées automatiquement)
├── tailwind.config.ts
├── next.config.ts
└── package.json
Le Code (Après)
// app/layout.tsx
import { Inter } from 'next/font/google';
import { Header } from '@/components/Header';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'Restaurant Le Bon Goût',
description: 'Cuisine traditionnelle française depuis 1985',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fr">
<body className={inter.className}>
<Header />
<main>{children}</main>
</body>
</html>
);
}
// components/Header.tsx
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
import { Menu, X } from 'lucide-react';
const navItems = [
{ href: '/', label: 'Accueil' },
{ href: '/menu', label: 'Menu' },
{ href: '/galerie', label: 'Galerie' },
{ href: '/contact', label: 'Contact' },
];
export function Header() {
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
return (
<header className="fixed top-0 w-full bg-white/90 backdrop-blur-md z-50 border-b">
<nav className="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
<Link href="/" className="text-xl font-semibold">
Le Bon Goût
</Link>
{/* Desktop */}
<ul className="hidden md:flex gap-8">
{navItems.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className={`transition-colors ${
pathname === item.href
? 'text-amber-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{item.label}
</Link>
</li>
))}
</ul>
{/* Mobile */}
<button
className="md:hidden"
onClick={() => setIsOpen(!isOpen)}
aria-label="Menu"
>
{isOpen ? <X /> : <Menu />}
</button>
</nav>
{isOpen && (
<ul className="md:hidden bg-white border-t px-4 py-4 space-y-4">
{navItems.map((item) => (
<li key={item.href}>
<Link
href={item.href}
onClick={() => setIsOpen(false)}
className="block py-2"
>
{item.label}
</Link>
</li>
))}
</ul>
)}
</header>
);
}
// components/ContactForm.tsx
'use client';
import { useActionState } from 'react';
import { sendMessage } from '@/lib/actions';
export function ContactForm() {
const [state, formAction, isPending] = useActionState(sendMessage, null);
return (
<form action={formAction} className="space-y-6 max-w-md">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Nom
</label>
<input
type="text"
id="name"
name="name"
required
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
Message
</label>
<textarea
id="message"
name="message"
rows={4}
required
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500"
/>
</div>
<button
type="submit"
disabled={isPending}
className="w-full bg-amber-600 text-white py-3 rounded-lg hover:bg-amber-700 disabled:opacity-50"
>
{isPending ? 'Envoi...' : 'Envoyer'}
</button>
{state?.success && (
<p className="text-green-600">Message envoyé avec succès !</p>
)}
{state?.error && (
<p className="text-red-600">{state.error}</p>
)}
</form>
);
}
// lib/actions.ts
'use server';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2),
email: z.string().email(),
message: z.string().min(10),
});
export async function sendMessage(prevState: any, formData: FormData) {
const validated = schema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
});
if (!validated.success) {
return { error: 'Données invalides' };
}
// Envoi email via Resend, SendGrid, etc.
// await resend.emails.send({ ... });
return { success: true };
}
Le Déploiement (2025)
# Déploiement via Vercel CLI
vercel --prod
# Temps total : 45 secondes
# Rollback disponible en 1 clic
# Preview automatique sur chaque PR
# Analytics intégrés
# Certificat SSL automatique
Comparaison Détaillée
Taille et Performance
| Métrique | 2015 | 2025 | Amélioration | |----------|------|------|--------------| | Poids total (First Load) | 2.4 MB | 180 KB | -92% | | Requêtes HTTP | 28 | 8 | -71% | | Time to Interactive | 8.2s | 1.1s | -87% | | Score Lighthouse | ~35 | 98 | +180% |
Fonctionnalités Identiques, Implémentation Différente
| Fonctionnalité | 2015 | 2025 | |----------------|------|------| | Navigation responsive | Bootstrap navbar + jQuery | Composant React + CSS | | Animations scroll | jQuery custom | CSS scroll-driven animations | | Formulaire contact | jQuery Validate + PHP | Server Actions + Zod | | Galerie images | jQuery lightbox | Next.js Image + Dialog natif | | SEO | Meta tags manuels | Metadata API automatique | | Déploiement | FTP manuel | Git push + CI/CD auto |
Ce Qui a Disparu
- Prefixes vendor (-webkit-, -moz-, -ms-)
- Clearfix hacks
- jQuery pour tout
- Fichiers PHP séparés pour chaque action
- FTP/FileZilla
- Gestion manuelle du cache
- Bootstrap entier pour 3 composants
- Polyfills pour flexbox
- Fallbacks pour les features modernes
Ce Qui est Apparu
+ Composants réutilisables
+ TypeScript (type safety)
+ Server Components (SSR automatique)
+ Server Actions (forms sans API)
+ Optimisation images automatique
+ Code splitting automatique
+ Hot reload
+ Deploy previews
+ Edge functions
+ Analytics intégrés
Les Leçons de 10 Ans
1. La Complexité s'est Déplacée
2015 : Complexité dans le code (hacks CSS, jQuery spaghetti) 2025 : Complexité dans l'écosystème (tooling, configs, abstractions)
Le code est plus propre, mais il faut comprendre plus d'outils.
2. Les Fondamentaux Restent
Le HTML sémantique, le CSS bien structuré, le JavaScript propre — ces bases n'ont pas changé. Les outils pour y arriver, si.
3. L'Accessibilité Est Devenue Standard
En 2015, l'accessibilité était une "option". En 2025, c'est intégré par défaut dans les frameworks modernes.
4. La Performance Est Automatisée
Plus besoin d'optimiser manuellement les images, de configurer le cache, de minifier à la main. Les outils modernes le font automatiquement.
5. Le Déploiement N'est Plus une Épreuve
De "3 heures de stress avec FTP" à "git push et c'est en ligne". Le gain de temps est énorme.
Temps de Développement
| Phase | 2015 | 2025 |
|-------|------|------|
| Setup projet | 2h | 5min (npx create-next-app) |
| Structure HTML | 4h | 30min (composants) |
| Styling | 8h | 2h (Tailwind) |
| JavaScript/Interactivité | 6h | 1h (React) |
| Formulaire contact | 3h | 30min (Server Actions) |
| Tests cross-browser | 4h | 30min (standards) |
| Optimisation images | 2h | 0 (automatique) |
| Déploiement | 3h | 5min |
| Total | 32h | 5h |
Conclusion
Reconstruire ce site m'a rappelé à quel point le web a évolué. Ce qui prenait des jours prend maintenant des heures. Ce qui nécessitait des hacks a maintenant des solutions élégantes.
Mais surtout, cette expérience m'a montré que le "moi de 2015" faisait de son mieux avec les outils disponibles. Ces hacks jQuery et ces clearfix CSS n'étaient pas du mauvais code — c'était le standard de l'époque.
Dans 10 ans, notre code React/Next.js actuel semblera probablement aussi daté que mon jQuery de 2015. Et c'est normal. C'est le signe que notre industrie progresse.
Le meilleur code n'est pas celui qui sera éternel. C'est celui qui résout le problème aujourd'hui, avec les outils d'aujourd'hui, de manière maintenable.
Vous avez un site qui date de quelques années ? Parlons-en — une refonte peut transformer votre présence web.
Prêt à lancer votre projet ?
Transformez vos idées en réalité avec un développeur passionné par la performance et le SEO. Discutons de votre projet dès aujourd'hui.


