|
| 1 | +# Etude de migration Next.js vers Astro |
| 2 | + |
| 3 | +## Contexte |
| 4 | + |
| 5 | +Le site LyonJS tourne sur Next.js 16 (App Router, React 19, standalone output) deploye sur Scaleway Serverless Containers. Ce document evalue la faisabilite et l'interet d'une migration vers Astro. |
| 6 | + |
| 7 | +## Pourquoi migrer ? |
| 8 | + |
| 9 | +### Avantages attendus |
| 10 | + |
| 11 | +- **Performance** : Astro genere du HTML statique par defaut, zero JS cote client sauf pour les composants interactifs. Le site LyonJS est principalement du contenu statique, donc la majorite des pages n'aurait aucun JS client. |
| 12 | +- **Simplicite** : Pas de distinction server component / client component. Tous les composants Astro sont serveur par defaut, le JS client est opt-in via les directives `client:*`. |
| 13 | +- **Bundle size** : React n'est charge que pour les "islands" interactives (7 composants sur ~40), pas pour tout le site. |
| 14 | +- **MDX natif** : Astro supporte MDX nativement, sans config supplementaire. |
| 15 | +- **Build statique** : Astro est concu pour le SSG. Le mode SSR/ISR est possible mais le SSG est le mode par defaut, ce qui colle bien avec le profil du site. |
| 16 | +- **Independance framework** : Plus de dependance forte a React pour le rendu des pages. React n'est utilise que la ou il apporte de la valeur (interactivite). |
| 17 | +- **Taille de l'image Docker** : Sans le runtime Next.js/Node en mode standalone, l'image serait plus legere. |
| 18 | + |
| 19 | +### Inconvenients / risques |
| 20 | + |
| 21 | +- **Perte de l'ISR** : Next.js revalide les pages toutes les heures (`revalidate = 3600`). En SSG pur avec Astro, il faut rebuilder pour mettre a jour. Solutions : SSR mode, ou rebuild periodique via cron CI. |
| 22 | +- **Ecosysteme React reduit** : Les librairies React (`motion`, `yet-another-react-lightbox`, `react-markdown`) fonctionnent via les islands, mais c'est un pattern moins ergonomique que le tout-React. |
| 23 | +- **OG images dynamiques** : Next.js genere des images OG via `next/og` (edge runtime + JSX). Astro n'a pas d'equivalent natif, il faut utiliser `@vercel/og`, `satori`, ou generer les images au build. |
| 24 | +- **Courbe d'apprentissage** : Syntaxe `.astro` differente de JSX (frontmatter + template). Les contributeurs devront s'adapter. |
| 25 | +- **Middleware** : Pas d'equivalent direct au middleware Next.js. Les headers CSP devront etre configures au niveau du serveur (Scaleway / Docker) ou via Astro SSR middleware. |
| 26 | + |
| 27 | +## Inventaire des fonctionnalites a migrer |
| 28 | + |
| 29 | +### Pages et routes |
| 30 | + |
| 31 | +| Next.js (App Router) | Astro | Effort | |
| 32 | +|---|---|---| |
| 33 | +| `app/page.tsx` | `src/pages/index.astro` | Faible | |
| 34 | +| `app/a-propos/page.tsx` | `src/pages/a-propos.astro` | Faible | |
| 35 | +| `app/code-de-conduite/page.tsx` | `src/pages/code-de-conduite.astro` | Faible | |
| 36 | +| `app/devenir-sponsor/page.tsx` | `src/pages/devenir-sponsor.astro` | Faible | |
| 37 | +| `app/budget-et-financement/page.tsx` | `src/pages/budget-et-financement.astro` | Faible | |
| 38 | +| `app/evenements-precedents/page.tsx` | `src/pages/evenements-precedents/index.astro` | Faible | |
| 39 | +| `app/evenements-precedents/[year]/page.tsx` | `src/pages/evenements-precedents/[year].astro` avec `getStaticPaths()` | Moyen | |
| 40 | +| `app/evenement/[slug]/page.tsx` | `src/pages/evenement/[slug].astro` avec `getStaticPaths()` | Moyen | |
| 41 | +| `app/lyonjs-100/page.tsx` | `src/pages/lyonjs-100.astro` | Faible | |
| 42 | +| `app/not-found.tsx` | `src/pages/404.astro` | Faible | |
| 43 | +| `app/layout.tsx` | `src/layouts/Layout.astro` | Moyen | |
| 44 | +| `app/robots.ts` | `src/pages/robots.txt.ts` | Faible | |
| 45 | +| `app/sitemap.ts` | `@astrojs/sitemap` integration | Faible | |
| 46 | + |
| 47 | +### Data fetching |
| 48 | + |
| 49 | +| Pattern Next.js | Equivalent Astro | Notes | |
| 50 | +|---|---|---| |
| 51 | +| `generateStaticParams()` | `getStaticPaths()` | Meme concept, syntaxe differente | |
| 52 | +| `revalidate = 3600` (ISR) | Rebuild CI ou SSR mode | Pas d'ISR en SSG, voir section dediee | |
| 53 | +| `React cache()` | Appel direct dans le frontmatter | Pas de deduplication automatique, mais les appels sont dans le frontmatter donc executes une seule fois | |
| 54 | +| Server components async | Composants Astro (async par defaut) | Transition naturelle | |
| 55 | + |
| 56 | +### Composants interactifs (React islands) |
| 57 | + |
| 58 | +Ces 7 composants necessitent du JS client et deviendraient des islands React dans Astro : |
| 59 | + |
| 60 | +| Composant | Utilise | Directive Astro | |
| 61 | +|---|---|---| |
| 62 | +| `HomeHero.tsx` | motion (animations) | `client:load` | |
| 63 | +| `Number.tsx` | motion, next/font | `client:visible` | |
| 64 | +| `Collapsible.tsx` | useState | `client:visible` | |
| 65 | +| `NavLink.tsx` | next/navigation (usePathname) | `client:load` | |
| 66 | +| `MobileNavigation.tsx` | useState, usePathname, useEffect | `client:load` | |
| 67 | +| `PhotoAlbum.tsx` | useState, dynamic import lightbox | `client:visible` | |
| 68 | +| `EventCard.tsx` | dynamic import react-markdown | `client:visible` | |
| 69 | + |
| 70 | +**Note** : `NavLink` et `MobileNavigation` utilisent `usePathname()` de `next/navigation`. A remplacer par `window.location.pathname` ou `Astro.url.pathname` (partie serveur) + event listeners cote client. |
| 71 | + |
| 72 | +### Images |
| 73 | + |
| 74 | +| Next.js | Astro | |
| 75 | +|---|---| |
| 76 | +| `next/image` (8 fichiers) | `astro:assets` `<Image>` component | |
| 77 | +| `remotePatterns` dans next.config | `image.domains` dans astro.config | |
| 78 | +| Optimisation automatique (sharp) | Optimisation automatique (sharp) | |
| 79 | + |
| 80 | +Le composant `<Image>` d'Astro offre les memes fonctionnalites (lazy loading, format moderne, responsive). Migration directe. |
| 81 | + |
| 82 | +### MDX |
| 83 | + |
| 84 | +| Next.js | Astro | |
| 85 | +|---|---| |
| 86 | +| `@next/mdx` + `mdx-components.tsx` | `@astrojs/mdx` integration | |
| 87 | +| 5 fichiers `.mdx` | Meme fichiers, syntaxe compatible | |
| 88 | +| Custom components mapping | Components passees via props dans le layout | |
| 89 | + |
| 90 | +Astro a un excellent support MDX natif. Les composants React embarques dans le MDX (`<Orgas />`, `<Socials />`) peuvent rester en React via les islands ou etre convertis en composants Astro. |
| 91 | + |
| 92 | +### Metadata / SEO |
| 93 | + |
| 94 | +| Next.js | Astro | |
| 95 | +|---|---| |
| 96 | +| `export const metadata: Metadata` | `<head>` dans le layout + props | |
| 97 | +| `generateMetadata()` | Props dynamiques dans le layout | |
| 98 | +| `opengraph-image.tsx` (6 fichiers) | `satori` + `sharp` au build (voir ci-dessous) | |
| 99 | +| `robots.ts` | `src/pages/robots.txt.ts` | |
| 100 | +| `sitemap.ts` | `@astrojs/sitemap` | |
| 101 | +| JSON-LD structured data | `<script type="application/ld+json">` dans le layout | |
| 102 | + |
| 103 | +### OG Images dynamiques |
| 104 | + |
| 105 | +C'est le point le plus complexe. Actuellement 6 routes generent des images OG via `next/og` (basee sur `satori`). |
| 106 | + |
| 107 | +**Options avec Astro** : |
| 108 | +1. **`astro-og-canvas`** : Librairie communautaire pour generer des OG images au build |
| 109 | +2. **`satori` + `sharp` directement** : Meme moteur que `next/og`, utilisable dans `getStaticPaths()` pour generer les images au build |
| 110 | +3. **Service externe** : Generer via un endpoint API separe |
| 111 | + |
| 112 | +Recommandation : option 2, generer les images au build avec `satori` directement. |
| 113 | + |
| 114 | +### Middleware / Headers de securite |
| 115 | + |
| 116 | +Le `middleware.ts` actuel gere : |
| 117 | +- Nonce CSP pour les scripts inline |
| 118 | +- Headers de securite (X-Frame-Options, etc.) |
| 119 | + |
| 120 | +**Avec Astro** : |
| 121 | +- Les headers statiques (X-Frame-Options, etc.) → configures dans le Dockerfile / reverse proxy / `astro.config.mjs` en mode SSR |
| 122 | +- Le nonce CSP → plus necessaire si pas de scripts inline. Sinon, middleware Astro en mode SSR. |
| 123 | +- Alternative : configurer les headers directement dans Scaleway Serverless Containers |
| 124 | + |
| 125 | +### Redirections |
| 126 | + |
| 127 | +Une seule redirection (`/lyonjs-100/programme` → `/lyonjs-100`) → `redirects` dans `astro.config.mjs`. |
| 128 | + |
| 129 | +### Styles |
| 130 | + |
| 131 | +| Next.js | Astro | |
| 132 | +|---|---| |
| 133 | +| CSS Modules (20+ fichiers) | CSS Modules supportes nativement | |
| 134 | +| `globals.css` | Import global dans le layout | |
| 135 | +| `normalize.css` | Import dans le layout | |
| 136 | +| `classnames` | Continue a fonctionner | |
| 137 | + |
| 138 | +Migration transparente, rien a changer. |
| 139 | + |
| 140 | +## Strategie ISR vs rebuild |
| 141 | + |
| 142 | +Le site utilise `revalidate = 3600` sur plusieurs pages pour actualiser les donnees Meetup toutes les heures. |
| 143 | + |
| 144 | +**Options avec Astro** : |
| 145 | + |
| 146 | +| Option | Description | Impact | |
| 147 | +|---|---|---| |
| 148 | +| **SSG + rebuild cron** | Workflow CI schedule qui rebuild et redeploy toutes les heures | Simple, pas de serveur Node, image Docker minimale (nginx) | |
| 149 | +| **SSR mode** | Astro en mode `output: 'server'` avec cache headers | Garde le comportement ISR, mais necessite Node.js runtime | |
| 150 | +| **Hybride** | `output: 'hybrid'` — pages statiques par defaut, quelques routes SSR | Meilleur compromis | |
| 151 | + |
| 152 | +**Recommandation** : SSG + rebuild cron. Le contenu change rarement (quelques meetups par mois). Un rebuild horaire ou quotidien suffit largement. Cela permet de deployer une image Docker statique (nginx) au lieu d'un runtime Node.js. |
| 153 | + |
| 154 | +## Plan de migration |
| 155 | + |
| 156 | +### Phase 1 : Setup et infrastructure |
| 157 | + |
| 158 | +- [ ] Initialiser le projet Astro avec `@astrojs/react`, `@astrojs/mdx`, `@astrojs/sitemap` |
| 159 | +- [ ] Configurer `astro.config.mjs` (images, redirections, site URL) |
| 160 | +- [ ] Creer le layout principal (`Layout.astro`) avec les meta, les styles globaux, le JSON-LD |
| 161 | +- [ ] Configurer les headers de securite dans le Dockerfile ou la config Astro |
| 162 | +- [ ] Adapter le Dockerfile (build statique → nginx ou SSR → Node) |
| 163 | + |
| 164 | +### Phase 2 : Pages statiques |
| 165 | + |
| 166 | +- [ ] Migrer les 5 pages MDX (a-propos, code-de-conduite, devenir-sponsor, budget, lyonjs-100) |
| 167 | +- [ ] Migrer les composants de layout (Header, Footer, Navigation) |
| 168 | +- [ ] Migrer le systeme de navigation (NavLink, MobileNavigation → islands React ou composants Astro natifs) |
| 169 | +- [ ] Migrer la page 404 |
| 170 | + |
| 171 | +### Phase 3 : Pages dynamiques |
| 172 | + |
| 173 | +- [ ] Migrer le module Meetup API (graphql-request, queries) — reutilisable tel quel |
| 174 | +- [ ] Migrer `data-override.ts` et `overrideEvent.ts` — reutilisable tel quel |
| 175 | +- [ ] Migrer la homepage (NextEvent, LastReplays, Numbers, HomeHero) |
| 176 | +- [ ] Migrer `/evenements-precedents` et `/evenements-precedents/[year]` avec `getStaticPaths()` |
| 177 | +- [ ] Migrer `/evenement/[slug]` avec `getStaticPaths()` |
| 178 | + |
| 179 | +### Phase 4 : Composants interactifs |
| 180 | + |
| 181 | +- [ ] Convertir HomeHero et Number en islands React (`client:visible`) |
| 182 | +- [ ] Convertir Collapsible en island ou composant Astro natif (`<details>`) |
| 183 | +- [ ] Convertir PhotoAlbum + lightbox en island React |
| 184 | +- [ ] Convertir MobileNavigation (remplacer `usePathname` par du JS vanilla ou island) |
| 185 | + |
| 186 | +### Phase 5 : SEO et finitions |
| 187 | + |
| 188 | +- [ ] Generer les OG images au build avec `satori` |
| 189 | +- [ ] Configurer `@astrojs/sitemap` |
| 190 | +- [ ] Migrer `robots.txt` |
| 191 | +- [ ] Verifier les meta tags et le JSON-LD sur toutes les pages |
| 192 | +- [ ] Mettre a jour les tests Playwright (les selectors devraient rester les memes) |
| 193 | + |
| 194 | +### Phase 6 : CI/CD |
| 195 | + |
| 196 | +- [ ] Adapter le Dockerfile pour Astro |
| 197 | +- [ ] Si SSG pur : optionnel — remplacer le runtime Node.js par nginx pour une image plus legere |
| 198 | +- [ ] Ajouter un workflow cron pour rebuild periodique (si SSG) |
| 199 | +- [ ] Verifier que les deploys Scaleway fonctionnent |
| 200 | + |
| 201 | +## Estimation de la complexite |
| 202 | + |
| 203 | +| Element | Fichiers | Complexite | |
| 204 | +|---|---|---| |
| 205 | +| Pages statiques (MDX) | 5 | Faible | |
| 206 | +| Layout + Header + Footer | 3 | Faible | |
| 207 | +| Navigation (mobile, links) | 3 | Moyenne | |
| 208 | +| Homepage (hero, numbers, replays) | 5 | Moyenne | |
| 209 | +| Pages dynamiques (events, years) | 4 | Moyenne | |
| 210 | +| Module Meetup API | 5 | Faible (reutilisable) | |
| 211 | +| Data files | 9 | Aucune (reutilisable) | |
| 212 | +| OG images | 6 | Elevee | |
| 213 | +| Middleware → headers | 1 | Faible | |
| 214 | +| Tests Playwright | 3 | Faible (memes URLs) | |
| 215 | +| CI/CD + Docker | 3 | Faible | |
| 216 | + |
| 217 | +**Code reutilisable sans modification** : tout le dossier `data/`, le module `meetup/` (queries, API), les utilitaires (`dateUtils`, `slugify`, `overrideEvent`), les types, `classnames`, CSS Modules. |
| 218 | + |
| 219 | +**Code a reecrire** : les pages (syntaxe `.astro`), les composants de layout, les OG images. |
| 220 | + |
| 221 | +**Code a adapter** : les 7 composants React interactifs (ajouter `client:*` directives). |
| 222 | + |
| 223 | +## Conclusion |
| 224 | + |
| 225 | +La migration est faisable et presente un interet reel pour ce type de site (contenu statique, peu d'interactivite). Les principaux benefices sont la reduction du JS client, la simplification de l'architecture, et potentiellement une image Docker plus legere. |
| 226 | + |
| 227 | +Les points de friction sont les OG images dynamiques (necessite du travail custom avec satori) et la perte de l'ISR (compensable par un rebuild cron). |
| 228 | + |
| 229 | +Le code metier (API Meetup, data, types, utilitaires, CSS) est largement reutilisable. Le gros du travail est la reecriture des pages en syntaxe Astro et l'adaptation des composants interactifs en islands. |
0 commit comments