LSSD UI
v1.0.0

Guide · Next.js

LSSD UI dans un projet Next.js

En suivant ces étapes dans l’ordre, vous obtenez une application Next.js (App Router, TypeScript) qui tourne dans Docker, dont les pages utilisent LSSD UI, avec le rechargement à chaud pendant que vous travaillez. Comptez une demi-heure. Vous n’installez rien d’autre que Docker : Node.js tourne dans un conteneur.

Ce que vous allez construire

Les fichiers que vous allez créer ou modifier :

mon-outil/
├── app/
│   ├── globals.css         ← importe Tailwind puis LSSD UI
│   ├── layout.tsx          ← squelette commun : langue, thème, polices
│   └── page.tsx            ← la page d’accueil
├── public/
│   └── theme.js            ← thème clair ou sombre
├── next.config.ts          ← deux lignes à ajouter
├── Dockerfile
├── compose.yaml            ← production
├── compose.override.yaml   ← développement
├── .env.example
├── .gitignore              ← une ligne à ajouter
└── .dockerignore

Comment ça s’articule :

  • create-next-app installe et branche Tailwind CSS pour vous.
  • Le Dockerfile télécharge une version précise de LSSD UI, vérifie son empreinte et la place dans vendor/lssd-ui/.
  • app/globals.css importe Tailwind puis LSSD UI ; Next.js compile le tout avec vos composants.
  • Rien n’est chargé depuis ui.lssd.fr dans le navigateur.

Étape 1 — Créer le projet Next.js

Créez un dossier vide pour votre outil, ouvrez un terminal dedans, puis lancez cette commande. Elle démarre un conteneur Node.js jetable et y lance create-next-app avec TypeScript, Tailwind, ESLint et l’App Router :

docker run --rm -v "${PWD}:/app" -w /app node:22-alpine npx --yes create-next-app@latest . --ts --tailwind --eslint --app --no-src-dir --import-alias "@/*" --use-npm --yes

Vérification : le terminal affiche Success! Created app at /app, et le dossier contient package.json, package-lock.json, next.config.ts et un dossier app/. Un dossier node_modules a aussi été créé : il ne sert pas, Docker installera ses propres dépendances.

Sous Linux, les fichiers créés appartiennent à root. Ajoutez --user "$(id -u):$(id -g)" juste après docker run pour qu’ils soient à vous.

Étape 2 — Brancher LSSD UI dans le CSS

Remplacez tout le contenu de app/globals.css par ces trois lignes. Le fichier créé par create-next-app définit ses propres couleurs et son propre thème sombre : ils entreraient en conflit avec LSSD UI.

/* Tailwind first, then LSSD UI. Nothing else: colours, fonts and themes come from LSSD UI. */
@import "tailwindcss";
@import "../vendor/lssd-ui/index.css";

Le dossier vendor n’existe pas sur votre disque : c’est l’image Docker qui l’apporte (étape 6). C’est normal.

Étape 3 — Le squelette commun

3.1 Le script du thème

Créez public/theme.js. Il choisit le thème clair ou sombre avant l’affichage de la page, pour éviter un flash de la mauvaise couleur :

// Sets data-theme before the first paint: the stored choice, otherwise the system setting.
(function () {
  var stored = null;
  try { stored = localStorage.getItem("lssd-theme"); } catch (error) {}
  var dark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
  document.documentElement.setAttribute("data-theme", stored || (dark ? "dark" : "light"));
})();

3.2 Le layout

Remplacez tout le contenu de app/layout.tsx. Ce fichier enveloppe toutes les pages : langue française, thème, polices LSSD et couleurs de fond et de texte. Les polices Geist de create-next-app disparaissent : LSSD utilise General Sans et Cabinet Grotesk.

import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "LSSD Exemple",
  description: "Un outil LSSD.",
};

export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    // theme.js changes data-theme before React starts: tell React it is expected.
    <html lang="fr" data-theme="light" suppressHydrationWarning>
      <head>
        <link rel="preconnect" href="https://api.fontshare.com" />
        <link rel="preconnect" href="https://cdn.fontshare.com" crossOrigin="anonymous" />
        <link
          rel="stylesheet"
          href="https://api.fontshare.com/v2/css?f[]=general-sans@400,500,600,700&f[]=cabinet-grotesk@500,700,800&display=swap"
        />
        {/* Must run before the first paint, so a plain blocking script on purpose. */}
        {/* eslint-disable-next-line @next/next/no-sync-scripts */}
        <script src="/theme.js" />
      </head>
      <body className="bg-bg text-ink font-sans antialiased">
        {children}
      </body>
    </html>
  );
}
  • suppressHydrationWarning : theme.js change data-theme avant que React démarre ; cet attribut indique à React que c’est voulu.
  • Le script du thème est un <script> classique dans le <head>, pas le composant Script de Next.js : il doit s’exécuter avant le premier affichage, ce que Script ne garantit pas.

Étape 4 — La page d’accueil

Remplacez tout le contenu de app/page.tsx. En JSX, l’attribut class s’écrit className ; les liens internes passent par Link de Next.js.

import Link from "next/link";

export default function Home() {
  return (
    <>
      <header className="lssd-header">
        <Link className="lssd-header__brand" href="/">
          <span className="lssd-header__name">LSSD</span>
          <span className="lssd-header__product">Exemple</span>
        </Link>
        <div className="lssd-header__end">
          <Link className="lssd-btn lssd-btn--primary lssd-btn--sm" href="/">
            Ajouter
          </Link>
        </div>
      </header>
      <main className="mx-auto w-full max-w-content px-4 sm:px-6 py-8">
        <h1 className="title-1">Bonjour</h1>
        <article className="lssd-card mt-6">
          <p className="lssd-card__overline">LSSD UI</p>
          <h2 className="lssd-card__title">Ça marche</h2>
          <p className="lssd-card__body">Cette page utilise les classes de LSSD UI.</p>
          <div className="lssd-card__actions">
            <button type="button" className="lssd-btn lssd-btn--primary">
              Enregistrer
            </button>
          </div>
        </article>
      </main>
    </>
  );
}

Vous pouvez supprimer les images de démonstration de public/ (next.svg, vercel.svg…), qui ne servent plus.

Étape 5 — Régler Next.js

Ouvrez next.config.ts et ajoutez ces deux réglages dans l’objet nextConfig, sans toucher aux autres :

const nextConfig: NextConfig = {
  // Builds a self-contained server for the Docker image (.next/standalone).
  output: "standalone",
  // In Docker on Windows or macOS, file changes do not reach the container: poll for them.
  // NEXT_POLL is set by compose.override.yaml only.
  watchOptions: process.env.NEXT_POLL ? { pollIntervalMs: 1000 } : undefined,
  // … keep the options written by create-next-app
};
  • output: "standalone" produit un serveur autonome et léger pour l’image de production.
  • watchOptions fait vérifier les fichiers chaque seconde en développement : sans lui, Next.js ne voit pas vos modifications depuis Windows ou macOS, et la page ne se met pas à jour.

Étape 6 — Écrire le Dockerfile

Créez Dockerfile (sans extension) à la racine. Vous n’avez rien à y modifier, sauf la version de LSSD UI quand vous voudrez la mettre à jour.

# syntax=docker/dockerfile:1

# --- lssd-ui: the pinned LSSD UI library, checksum verified ---
FROM node:22-alpine AS lssd-ui
ARG LSSD_UI_VERSION=1.0.0
ARG LSSD_UI_SHA256=b34c40a92021933668b2da0fa9d52ab2b738d0a257890a8bc893260c3110c388
ADD --checksum=sha256:${LSSD_UI_SHA256} \
    https://ui.lssd.fr/releases/v${LSSD_UI_VERSION}/lssd-ui-${LSSD_UI_VERSION}.tar.gz /tmp/lssd-ui.tar.gz
RUN mkdir /out && tar -xzf /tmp/lssd-ui.tar.gz -C /out

# --- deps: node_modules, from the lock file ---
FROM node:22-alpine AS deps
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# --- dev: adds LSSD UI; the code is mounted by compose.override.yaml ---
FROM deps AS dev
COPY --from=lssd-ui /out/lssd-ui/ /app/vendor/lssd-ui/
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0"]

# --- build: compiles the application (Tailwind and LSSD UI included) ---
FROM deps AS build
COPY . .
COPY --from=lssd-ui /out/lssd-ui/ vendor/lssd-ui/
RUN npm run build

# --- prod: the standalone server only, run as a non-root user ---
FROM node:22-alpine AS prod
ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 HOSTNAME=0.0.0.0 PORT=3000
WORKDIR /app
COPY --from=build --chown=node:node /app/.next/standalone ./
COPY --from=build --chown=node:node /app/.next/static ./.next/static
COPY --from=build --chown=node:node /app/public ./public
USER node
EXPOSE 3000
CMD ["node", "server.js"]
ÉtapeRôle
lssd-uiTélécharge la version choisie de LSSD UI et vérifie son empreinte SHA-256. Si elle ne correspond pas, la construction s’arrête.
depsInstalle les dépendances exactes de package-lock.json.
devL’image de développement : les dépendances et LSSD UI.
buildCompile l’application, CSS compris.
prodL’image de production : uniquement le serveur autonome, lancé par l’utilisateur node, sans droits d’administration.

LSSD_UI_VERSION et LSSD_UI_SHA256 sont la version de LSSD UI et son empreinte. Ces deux valeurs se trouvent sur la page Versions.

Étape 7 — Écrire les fichiers Docker Compose

7.1 La production : compose.yaml

Il décrit le conteneur de production, joint par le Traefik partagé des serveurs LSSD. Remplacez exemple par le nom de votre outil dans les quatre lignes traefik.http.… : ce nom doit être unique sur le serveur.

services:
  web:
    build: { context: ., target: prod }
    restart: unless-stopped
    # No published port: only the shared Traefik reaches the site.
    networks: [web]
    labels:
      - traefik.enable=true
      - traefik.docker.network=shared-web
      - traefik.http.routers.exemple.rule=Host(`${APP_DOMAIN:?Set APP_DOMAIN in .env}`)
      - traefik.http.routers.exemple.entrypoints=websecure
      - traefik.http.routers.exemple.tls.certresolver=${TRAEFIK_CERTRESOLVER:-myresolver}
      - traefik.http.services.exemple.loadbalancer.server.port=3000

networks:
  web:
    external: true
    name: shared-web

7.2 Le développement : compose.override.yaml

Docker Compose le lit automatiquement sur votre ordinateur. Il lance next dev avec votre code monté dans le conteneur.

# Development only: loaded automatically by `docker compose up`.
services:
  web:
    build: { context: ., target: dev }
    ports: ["127.0.0.1:3000:3000"]
    environment:
      # File changes from a Windows or macOS host do not reach the container: poll instead.
      NEXT_POLL: "1"
    volumes:
      - .:/app
      # Keep the copies of the image visible under the mounted code.
      - /app/node_modules
      - /app/.next
      - /app/vendor
    # The shared Traefik network does not exist on a development machine.
    networks: !override [default]
    labels: !reset []

Les trois lignes - /app/node_modules, - /app/.next et - /app/vendor sont importantes : votre dossier est monté par-dessus celui de l’image, ce qui cacherait les dépendances et la copie de LSSD UI de l’image. Ces lignes les gardent visibles.

7.3 Les variables : .env.example et .env

Créez .env.example, qui documente les variables :

# Copy this file to .env and fill in the values. Never commit .env.
APP_DOMAIN=exemple.lssd.fr
TRAEFIK_CERTRESOLVER=myresolver

Copiez-le en .env (Compose en a besoin, même en développement). Sur le serveur, vous y mettrez les vraies valeurs ; .env n’est jamais commité.

Étape 8 — Ignorer les fichiers générés

À la fin de .gitignore (créé par create-next-app), ajoutez :

# LSSD UI, downloaded by the Docker image
/vendor/

Créez .dockerignore : ce que Docker ne doit pas copier dans l’image.

.git
.env*
node_modules
.next
vendor
npm-debug.log*

Étape 9 — Lancer en développement

docker compose up --build

La première fois, Docker télécharge Node.js et installe les dépendances : comptez quelques minutes. Quand le terminal affiche ✓ Ready, ouvrez http://localhost:3000. La première page met quelques secondes à se compiler.

Vérification :

  • La barre du haut « LSSD Exemple » et une carte « Ça marche » avec un bouton vert en pilule s’affichent.
  • Ajoutez text-accent-ink dans le className du <h1> de app/page.tsx et enregistrez : en une ou deux secondes, le titre devient vert sans recharger.
  • Passez votre système en thème sombre et rechargez : la page passe en sombre, sans flash blanc.

Pour arrêter : Ctrl+C. En arrière-plan : docker compose up -d, puis docker compose logs -f et docker compose down. Une commande npm se lance dans le conteneur : docker compose exec web npm run lint.

Pour ajouter un paquet, ne lancez pas npm install sur votre ordinateur. Utilisez docker compose exec web npm install nom-du-paquet, puis reconstruisez l’image avec docker compose up --build --renew-anon-volumes.

Étape 10 — Utiliser les composants

Les composants LSSD sont de simples classes : pas de composant React à importer. Un champ de formulaire, par exemple :

<div className="lssd-field">
  <label className="lssd-label" htmlFor="amount">Montant</label>
  <input className="lssd-input" id="amount" name="amount" inputMode="decimal" />
  <p className="lssd-help">Saisissez le montant TTC.</p>
</div>

En JSX, for devient htmlFor. Si vous réutilisez souvent un composant, créez votre propre composant React qui pose ces classes ; ne recopiez pas leurs styles en utilitaires. Tous les composants et leurs règles sont sur la page Composants.

Étape 11 — Mettre en production

Sur le serveur, dans le dossier du projet :

  1. Copiez .env.example en .env et indiquez le domaine.
  2. Construisez et lancez la production seule (sans compose.override.yaml) : docker compose -f compose.yaml up -d --build.
  3. Le Traefik partagé publie le site en HTTPS sur le domaine de APP_DOMAIN.

Pour tester l’image de production sur votre ordinateur avant de déployer : docker compose -f compose.yaml build. Si la construction réussit, l’application et son CSS ont été compilés.

Mettre à jour LSSD UI

  1. Sur la page Versions, lisez les notes de la nouvelle version et copiez son numéro et son empreinte.
  2. Dans le Dockerfile, remplacez LSSD_UI_VERSION et LSSD_UI_SHA256.
  3. Relancez le développement avec docker compose up --build --renew-anon-volumes (--renew-anon-volumes remplace l’ancienne copie de LSSD UI par la nouvelle).
  4. Vérifiez vos pages dans les deux thèmes, puis déployez.

En cas de problème

SymptômeCause et solution
Can't resolve '../vendor/lssd-ui/index.css'La copie de LSSD UI n’est pas visible dans le conteneur. Vérifiez la ligne - /app/vendor de compose.override.yaml, puis lancez docker compose up --build --renew-anon-volumes.
La page ne se met pas à jour quand vous enregistrez.Vérifiez le réglage watchOptions de next.config.ts (étape 5) et la variable NEXT_POLL de compose.override.yaml, puis relancez docker compose up.
digest mismatch pendant la constructionL’empreinte ne correspond pas à la version. Recopiez les deux valeurs depuis la page Versions.
Les couleurs ou les polices ne sont pas celles de LSSD.Il reste le contenu d’origine dans app/globals.css ou les polices Geist dans app/layout.tsx. Reprenez les étapes 2 et 3.
Une classe Tailwind n’a aucun effet.La classe n’existe pas dans LSSD UI : bg-zinc-50 ou text-gray-700 sont retirées volontairement. Utilisez les couleurs de la page Fondations.
port is already allocatedUn autre programme utilise le port 3000. Dans compose.override.yaml, remplacez 127.0.0.1:3000:3000 par 127.0.0.1:3010:3000 et ouvrez http://localhost:3010.
npm ci échoue pendant la construction.package-lock.json ne correspond plus à package.json. Mettez-le à jour avec docker run --rm -v "${PWD}:/app" -w /app node:22-alpine npm install, puis reconstruisez.