Astro Tailwind v4 + React
Cette variante garde la même architecture que test-bootstrap, mais remplace Bootstrap/jQuery par Tailwind v4 et React.
Astro est responsable des pages, du layout et du build statique. React sert à écrire des composants .tsx. Tailwind sert au style. Le JavaScript global reste possible pour des comportements non React, mais il ne doit pas dupliquer une interaction déjà gérée par React.
Structure cible
Section titled “Structure cible”Reprendre la même séparation que le projet Bootstrap.
test-tailwind-react/ astro.config.mjs package.json public/ favicon.ico favicon.svg navbar.js src/ assets/ hero.svg logo.svg components/ Hero.tsx Navbar.tsx layouts/ Layout.astro pages/ index.astro styles/ custom.css env.d.tsLes rôles restent stables :
src/pages/déclare les routes Astro.src/layouts/Layout.astrocontient le document HTML commun.src/components/*.tsxcontient les composants React.src/styles/custom.cssimporte Tailwind et les styles projet.public/navbar.jspeut contenir un JavaScript global livré tel quel dansdist/.src/assets/contient les images importées dans Astro ou React.public/contient les favicons et fichiers servis sans transformation.
Installation
Section titled “Installation”Créer Astro avec React, puis installer Tailwind v4.
npm create astro@latestnpx astro add reactnpm install tailwindcss @tailwindcss/viteDépendances minimales :
{ "type": "module", "scripts": { "dev": "astro dev", "build": "astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/react": "^5.0.0", "@tailwindcss/vite": "^4.0.0", "astro": "^6.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwindcss": "^4.0.0" }}Configuration Astro
Section titled “Configuration Astro”Ajouter React et Tailwind au build Astro.
// @ts-checkimport { defineConfig } from "astro/config";import react from "@astrojs/react";import tailwindcss from "@tailwindcss/vite";
export default defineConfig({ output: "static", integrations: [react()], build: { assetsPrefix: ".", }, vite: { plugins: [tailwindcss()], build: { cssCodeSplit: true, assetsInlineLimit: 0, }, },});Points importants :
react()permet d’utiliser des fichiers.tsx.tailwindcss()active Tailwind v4 dans Vite.output: "static"garde une sortie HTML statique.- Sans directive
client:*, les composants React sont rendus en HTML au build.
CSS global
Section titled “CSS global”Dans Tailwind v4, le CSS global importe Tailwind directement.
@import "tailwindcss";
@theme { --font-sans: Inter, ui-sans-serif, system-ui, sans-serif;}
.test-text { color: #b45309; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;}Conventions :
- garder le nom
custom.csspour rester proche detest-bootstrap; - mettre
@import "tailwindcss";en première ligne ; - réserver
@themeaux tokens projet ; - garder les classes utilitaires Tailwind dans les composants
.tsx; - utiliser des classes CSS nommées seulement quand elles représentent une convention projet.
Layout global
Section titled “Layout global”Le layout importe custom.css, pose les balises globales et expose le slot.
---import "../styles/custom.css";---
<!doctype html><html lang="fr"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <link rel="icon" type="image/svg+xml" href="./favicon.svg" /> <link rel="icon" href="./favicon.ico" /> <meta name="generator" content={Astro.generator} /> <title>Astro Tailwind React</title> </head> <body class="min-h-screen bg-slate-950 text-white antialiased"> <slot /> <script is:inline src="./navbar.js"></script> </body></html>Le script global est optionnel dans cette variante. S’il doit être livré tel quel, le placer dans public/.
Le garder si :
- un comportement doit rester hors React ;
- le back doit reprendre un fichier JavaScript classique ;
- l’interaction ne justifie pas une hydratation React.
Le retirer si :
- tous les composants sont statiques ;
- l’interaction est gérée par un composant React hydraté ;
- le build final doit contenir le moins de JavaScript possible.
Page Astro
Section titled “Page Astro”La page Astro assemble les composants React.
---import Hero from "../components/Hero";import Navbar from "../components/Navbar";import Layout from "../layouts/Layout.astro";---
<Layout> <Navbar /> <Hero /></Layout>Composant React statique
Section titled “Composant React statique”Un composant .tsx peut remplacer un composant .astro quand le projet préfère React pour le templating.
import type { FC } from "react";
const Hero: FC = () => { return ( <main className="bg-blue-700 text-white"> <section className="mx-auto max-w-5xl px-6 py-20"> <h1 className="text-5xl font-semibold tracking-normal">Bienvenue !</h1> <p className="mt-4 max-w-2xl text-lg text-blue-100"> Ceci est un hero simple construit avec Astro, Tailwind v4 et React. </p> <p className="test-text mt-6"> Tailwind gère la mise en page, les couleurs et la typographie. </p> <button className="mt-8 rounded-md bg-white px-4 py-2 text-sm font-medium text-blue-700" type="button" > En savoir plus </button> </section> </main> );};
export default Hero;Ce composant est statique tant que la page l’utilise comme ceci :
<Hero />Il devient hydraté si la page l’utilise comme ceci :
<Hero client:load />Navbar React statique
Section titled “Navbar React statique”Pour une sortie HTML statique, la navbar React ne doit pas dépendre de useState.
import type { FC } from "react";
const Navbar: FC = () => { return ( <nav className="bg-slate-950 text-white"> <div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-4"> <span className="text-base font-semibold">Mon Site</span> <button id="navbar-toggle" className="rounded-md border border-white/20 px-3 py-2 text-sm lg:hidden" type="button" aria-controls="navbarNav" aria-expanded="false" > Menu </button> <div id="navbarNav" className="hidden items-center gap-6 lg:flex"> <button className="text-sm text-white" type="button"> Accueil </button> <button className="text-sm text-white/70" type="button"> À propos </button> <button className="text-sm text-white/70" type="button"> Contact </button> <button id="notif-btn" className="rounded-md border border-white/20 px-3 py-2 text-sm" type="button" > Notifications <span id="notif-count" className="ml-2 rounded bg-red-600 px-2 py-0.5"> 3 </span> </button> </div> </div> </nav> );};
export default Navbar;Dans ce modèle, l’ouverture mobile et le compteur peuvent rester dans public/navbar.js.
document.addEventListener("DOMContentLoaded", function () { var count = 3; var notifBtn = document.getElementById("notif-btn"); var notifCount = document.getElementById("notif-count"); var navbarToggle = document.getElementById("navbar-toggle"); var navbarNav = document.getElementById("navbarNav");
if (notifBtn && notifCount) { notifBtn.addEventListener("click", function () { count += 1; notifCount.textContent = String(count); }); }
if (navbarToggle && navbarNav) { navbarToggle.addEventListener("click", function () { var isOpen = navbarNav.classList.toggle("flex"); navbarNav.classList.toggle("hidden", !isOpen); navbarToggle.setAttribute("aria-expanded", String(isOpen)); }); }});Cette version n’a pas besoin de jQuery.
React hydraté
Section titled “React hydraté”Si l’interaction doit rester dans React, mettre l’état dans le composant et hydrater explicitement.
import { useState, type FC } from "react";
const Navbar: FC = () => { const [isOpen, setIsOpen] = useState(false); const [count, setCount] = useState(3);
return ( <nav className="bg-slate-950 text-white"> <button type="button" onClick={() => setIsOpen((value) => !value)}> Menu </button> <div className={isOpen ? "block" : "hidden"}> <button type="button" onClick={() => setCount((value) => value + 1)}> Notifications <span>{count}</span> </button> </div> </nav> );};
export default Navbar;Et dans la page :
<Navbar client:load />Choisir cette option seulement si le projet accepte du JavaScript React dans le navigateur.
Images et assets
Section titled “Images et assets”Deux cas :
src/assets/pour les images importées par Astro ou React ;public/pour les fichiers servis tels quels.
Dans un composant React, importer une image depuis src/assets/.
import heroUrl from "../assets/hero.svg";
export default function Hero() { return <img src={heroUrl.src} alt="" />;}Pour un favicon ou un fichier qui doit garder son chemin, utiliser public/.
public/ favicon.ico favicon.svgBuild et sortie
Section titled “Build et sortie”Lancer :
npm run buildSortie attendue :
dist/ index.html favicon.ico favicon.svg _astro/ index@_@astro.[hash].css client.[hash].jsLe fichier client.[hash].js peut exister sans être chargé par la page. Ce qui compte est le HTML final :
- si aucun composant n’a
client:*, le HTML contient le rendu statique ; - si un composant a
client:load, le HTML charge le runtime nécessaire ; - si
navbar.jsest utilisé, vérifier qu’il est bien copié dansdist/navbar.jset chargé depuisdist/index.html.
Quand choisir cette variante
Section titled “Quand choisir cette variante”Choisir Tailwind v4 + React + .tsx si :
- le projet veut composer plus vite avec React ;
- les composants ont des props ou variantes plus lisibles en TSX ;
- le style doit être porté par des classes utilitaires ;
- le build doit rester statique par défaut ;
- l’hydratation React doit rester un choix explicite.
Références
Section titled “Références”- Tailwind v4 avec Astro : Install Tailwind CSS with Astro
- Astro React : @astrojs/react
- Directives client Astro : Template directives reference