RAICODE
ProcessusProjetsBlogOffresClientsContact
development

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.

Mustapha Hamadi
Développeur Full-Stack
11 décembre 2025
10 min read
J'ai Reconstruit Mon Premier Site de 2015 avec les Outils de 2025
#Évolution#Nostalgie#Technique
Partager :

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">&times;</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.

Partager :

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.

Demander un devis
Voir mes réalisations
Réponse < 48h
15+ projets livrés
100% satisfaction client

Table des matières

Articles similaires

Les 10 Lignes de Code les Plus Dangereuses (Avec Exemples Réels)
development

Les 10 Lignes de Code les Plus Dangereuses (Avec Exemples Réels)

10 décembre 2025
10 min read
5 Technologies Hyped que Je Refuse d'Utiliser en Production (Et Pourquoi)
development

5 Technologies Hyped que Je Refuse d'Utiliser en Production (Et Pourquoi)

8 décembre 2025
12 min read
De 0 à Production en 4 Heures : Speed-Run d'un Site E-commerce
development

De 0 à Production en 4 Heures : Speed-Run d'un Site E-commerce

8 décembre 2025
15 min read
RAICODE

Développeur Full-Stack spécialisé en Next.js & React.
Je crée des applications web performantes et sur mesure.

SERVICES

  • Sites Vitrines
  • Applications SaaS
  • E-commerce
  • API & Backend

NAVIGATION

  • Processus
  • Projets
  • Blog
  • Tarifs
  • Contact

LÉGAL

  • Mentions légales
  • Confidentialité
  • CGU
  • CGV

© 2025 Raicode. Tous droits réservés.

Créé parRaicode.
↑ Retour en haut