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
└── .dockerignoreComment ça s’articule :
create-next-appinstalle et branche Tailwind CSS pour vous.- Le
Dockerfiletélécharge une version précise de LSSD UI, vérifie son empreinte et la place dansvendor/lssd-ui/. app/globals.cssimporte Tailwind puis LSSD UI ; Next.js compile le tout avec vos composants.- Rien n’est chargé depuis
ui.lssd.frdans 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.jschangedata-themeavant 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 composantScriptde Next.js : il doit s’exécuter avant le premier affichage, ce queScriptne 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.watchOptionsfait 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"]| Étape | Rôle |
|---|---|
lssd-ui | Té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. |
deps | Installe les dépendances exactes de package-lock.json. |
dev | L’image de développement : les dépendances et LSSD UI. |
build | Compile l’application, CSS compris. |
prod | L’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-web7.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-inkdans leclassNamedu<h1>deapp/page.tsxet 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 :
- Copiez
.env.exampleen.envet indiquez le domaine. - Construisez et lancez la production seule (sans
compose.override.yaml) :docker compose -f compose.yaml up -d --build. - 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
- Sur la page Versions, lisez les notes de la nouvelle version et copiez son numéro et son empreinte.
- Dans le
Dockerfile, remplacezLSSD_UI_VERSIONetLSSD_UI_SHA256. - Relancez le développement avec
docker compose up --build --renew-anon-volumes(--renew-anon-volumesremplace l’ancienne copie de LSSD UI par la nouvelle). - Vérifiez vos pages dans les deux thèmes, puis déployez.
En cas de problème
| Symptôme | Cause 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 construction | L’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 allocated | Un 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. |