This commit is contained in:
ordinarthur 2026-02-27 18:20:54 +01:00
parent 8be3338265
commit 5e540dc0bb
7 changed files with 325 additions and 8 deletions

241
CLAUDE.md Normal file
View File

@ -0,0 +1,241 @@
# REBOURS — Documentation technique
## Architecture du projet
```
rebours/
├── src/
│ ├── layouts/
│ │ └── Base.astro # Layout HTML commun (SEO, fonts, CSS)
│ └── pages/
│ ├── index.astro # Page principale (hero, collection, newsletter)
│ ├── collection/
│ │ └── [slug].astro # Pages produits statiques (SSG)
│ └── success.astro # Page de confirmation Stripe
├── public/
│ ├── style.css # CSS global
│ ├── main.js # JS client (cursor, grid, panel, routing)
│ ├── robots.txt # SEO
│ ├── sitemap.xml # SEO
│ └── assets/ # Images produits
├── server.mjs # Serveur API Fastify (Stripe)
├── astro.config.mjs # Config Astro (SSG, proxy dev)
├── nginx.conf # Config nginx de référence
└── .env # Variables d'environnement (non versionné)
```
## Stack
| Couche | Techno |
|--------|--------|
| Front (SSG) | Astro + HTML/CSS/JS vanilla |
| API | Fastify (Node.js) |
| Paiement | Stripe Checkout |
| Reverse proxy | Nginx |
| Hébergement | VPS (Debian) |
| Fonts | Space Mono (Google Fonts) |
---
## Développement local
### Prérequis
- Node.js ≥ 18
- Un fichier `.env` à la racine (voir `.env.example`)
### Variables d'environnement (.env)
```env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=http://localhost:4321
PORT=8888
```
> En dev, le serveur Fastify tourne sur le port **8888** (pour ne pas entrer en conflit avec d'autres services).
> Le proxy Vite dans `astro.config.mjs` redirige `/api/*``http://127.0.0.1:8888`.
### Lancer le projet
```bash
npm install
npm run dev
```
Cela lance en parallèle (via `concurrently`) :
- `astro dev` → http://localhost:4321
- `node --watch server.mjs` (mode dev, PORT=8888)
### Build
```bash
npm run build
# Génère ./dist/ (fichiers statiques Astro)
```
---
## Production
### Serveur : ordinarthur@10.10.0.13
### Architecture prod
```
Internet → Nginx (port 80) → /var/www/html/rebours/dist/ (fichiers statiques)
→ /api/* → proxy → Fastify :3000
```
### Chemins importants sur le serveur
| Quoi | Où |
|------|----|
| Fichiers web | `/var/www/html/rebours/dist/` |
| Projet complet | `/var/www/html/rebours/` |
| Config nginx | `/etc/nginx/sites-available/rebours` |
| Service systemd | `rebours.service` |
| Logs | `journalctl -u rebours -f` |
### Variables d'environnement en prod
Le fichier `.env` est dans `/var/www/html/rebours/.env` :
```env
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=https://rebours.studio
PORT=3000
```
### Service systemd
Le serveur Fastify est géré par systemd :
```bash
sudo systemctl status rebours
sudo systemctl restart rebours
sudo systemctl stop rebours
journalctl -u rebours -f # logs en temps réel
```
---
## Déploiement (mise à jour du site)
### 1. Build en local
```bash
npm run build
# Génère ./dist/
```
### 2. Envoyer les fichiers sur le serveur
Les fichiers statiques Astro :
```bash
scp -r dist/* ordinarthur@10.10.0.13:/tmp/rebours-dist/
ssh ordinarthur@10.10.0.13 "sudo cp -r /tmp/rebours-dist/* /var/www/html/rebours/dist/"
```
Si le server.mjs a changé :
```bash
scp server.mjs ordinarthur@10.10.0.13:/tmp/server.mjs
ssh ordinarthur@10.10.0.13 "sudo cp /tmp/server.mjs /var/www/html/rebours/server.mjs && sudo systemctl restart rebours"
```
### 3. Vérifier
```bash
ssh ordinarthur@10.10.0.13 "sudo nginx -t && sudo systemctl status rebours"
```
### Permissions (si problème 403 nginx)
```bash
ssh ordinarthur@10.10.0.13 "sudo chown -R www-data:www-data /var/www/html/rebours/dist"
```
---
## Nginx — référence
Le fichier `nginx.conf` à la racine du projet est la config de **référence**.
La config réelle sur le serveur est dans `/etc/nginx/sites-available/rebours`.
Pour mettre à jour la config nginx :
```bash
scp nginx.conf ordinarthur@10.10.0.13:/tmp/nginx-rebours.conf
ssh ordinarthur@10.10.0.13 "sudo cp /tmp/nginx-rebours.conf /etc/nginx/sites-available/rebours && sudo nginx -t && sudo systemctl reload nginx"
```
Points clés de la config :
- `root /var/www/html/rebours/dist` → fichiers statiques Astro
- `try_files $uri $uri/ $uri.html /index.html` → SPA fallback pour les routes Astro
- `/api/` → proxy vers Fastify sur `127.0.0.1:3000`
- HTML : `no-store` (jamais caché)
- CSS/JS/assets : `immutable` (hash dans le nom de fichier, cache 1 an)
---
## Routing
Le routing est hybride :
| URL | Comportement |
|-----|-------------|
| `/` | Page principale Astro |
| `/collection/lumiere-orbitale` | Page Astro SSG générée statiquement, ouvre le panel auto via `window.__OPEN_PANEL__` |
| `/collection/table-terrazzo` | idem |
| `/collection/module-serie` | idem |
| `/success?session_id=...` | Page de confirmation Stripe |
Quand on clique sur une carte produit depuis le navigateur (sans refresh) :
- Le panel s'ouvre
- `history.pushState` change l'URL → `/collection/{slug}`
- Le bouton retour du navigateur ferme le panel et revient à `/`
Quand on arrive directement sur `/collection/{slug}` (lien partagé, refresh) :
- Astro sert la page statique correspondante
- Un script inline lit `<meta name="x-open-panel">` et set `window.__OPEN_PANEL__`
- `main.js` lit `window.__OPEN_PANEL__` au DOMContentLoaded et ouvre le bon panel
---
## Ajouter un produit
### 1. Ajouter la carte dans `src/pages/index.astro` et `src/pages/collection/[slug].astro`
Copier un `<article class="product-card">` existant et modifier les `data-*`.
### 2. Ajouter le slug dans `[slug].astro`
Dans `getStaticPaths()`, ajouter une entrée dans le tableau `PRODUCTS` :
```js
{
slug: 'mon-nouveau-produit',
name: 'MON_PRODUIT',
title: 'REBOURS — Mon Produit | Collection 001',
description: 'Description pour le SEO.',
ogImage: 'https://rebours.studio/assets/mon-produit.jpg',
},
```
### 3. Ajouter l'image dans `public/assets/`
Format recommandé : JPG 1024×1024, < 300 Ko.
### 4. Ajouter le prix Stripe dans `server.mjs`
```js
const PRODUCTS = {
lumiere_orbitale: { price_id: 'price_xxx' },
mon_nouveau_produit: { price_id: 'price_yyy' }, // ← ajouter
}
```
### 5. Rebuild et déployer
```bash
npm run build
# puis déployer (voir section Déploiement)
```
---
## Stripe
- **Test** : utiliser les clés `sk_test_...` dans `.env`
- **Prod** : utiliser les clés `sk_live_...` dans `.env` sur le serveur
- La redirection après paiement va vers `${DOMAIN}/success?session_id=...`
- Le webhook Stripe doit pointer vers `https://rebours.studio/api/webhook`
- Le `STRIPE_WEBHOOK_SECRET` correspond au secret généré dans le dashboard Stripe pour ce webhook
---
## Fichiers à ne jamais versionner
- `.env` (clés Stripe, secrets)
- `node_modules/`
- `dist/` (généré par le build)

5
public/robots.txt Normal file
View File

@ -0,0 +1,5 @@
User-agent: *
Allow: /
Disallow: /success
Sitemap: https://rebours.studio/sitemap.xml

27
public/sitemap.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://rebours.studio/</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://rebours.studio/collection/lumiere-orbitale/</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://rebours.studio/collection/table-terrazzo/</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://rebours.studio/collection/module-serie/</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
</urlset>

View File

@ -10,7 +10,7 @@ const {
title, title,
description = 'REBOUR Studio crée du mobilier d\'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours.', description = 'REBOUR Studio crée du mobilier d\'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours.',
ogImage = 'https://rebours.studio/assets/lamp-violet.jpg', ogImage = 'https://rebours.studio/assets/lamp-violet.jpg',
canonical = 'https://rebour.studio/', canonical = 'https://rebours.studio/',
} = Astro.props; } = Astro.props;
--- ---

View File

@ -9,6 +9,9 @@ export function getStaticPaths() {
title: 'REBOURS — Solar Altar | Collection 001', title: 'REBOURS — Solar Altar | Collection 001',
description: 'Lampe de table unique. Béton texturé coulé à la main + dôme céramique laqué. Pièce unique fabriquée à Paris.', description: 'Lampe de table unique. Béton texturé coulé à la main + dôme céramique laqué. Pièce unique fabriquée à Paris.',
ogImage: 'https://rebours.studio/assets/lamp-violet.jpg', ogImage: 'https://rebours.studio/assets/lamp-violet.jpg',
productName: 'Solar Altar',
price: '1800',
availability: 'https://schema.org/LimitedAvailability',
}, },
{ {
slug: 'table-terrazzo', slug: 'table-terrazzo',
@ -16,6 +19,9 @@ export function getStaticPaths() {
title: 'REBOURS — TABLE TERRAZZO | Collection 001', title: 'REBOURS — TABLE TERRAZZO | Collection 001',
description: 'Table basse et étagère modulaire. Terrazzo fait main + acier tubulaire. Pièce unique fabriquée à Paris.', description: 'Table basse et étagère modulaire. Terrazzo fait main + acier tubulaire. Pièce unique fabriquée à Paris.',
ogImage: 'https://rebours.studio/assets/table-terrazzo.jpg', ogImage: 'https://rebours.studio/assets/table-terrazzo.jpg',
productName: 'Table Terrazzo',
price: null,
availability: 'https://schema.org/PreOrder',
}, },
{ {
slug: 'module-serie', slug: 'module-serie',
@ -23,20 +29,57 @@ export function getStaticPaths() {
title: 'REBOURS — MODULE SÉRIE | Collection 001', title: 'REBOURS — MODULE SÉRIE | Collection 001',
description: 'Série de 7 lampes béton colorées, dôme laqué et néon. Édition limitée fabriquée à Paris.', description: 'Série de 7 lampes béton colorées, dôme laqué et néon. Édition limitée fabriquée à Paris.',
ogImage: 'https://rebours.studio/assets/lampes-serie.jpg', ogImage: 'https://rebours.studio/assets/lampes-serie.jpg',
productName: 'Module Série',
price: null,
availability: 'https://schema.org/PreOrder',
}, },
]; ];
return PRODUCTS.map(p => ({ params: { slug: p.slug }, props: p })); return PRODUCTS.map(p => ({ params: { slug: p.slug }, props: p }));
} }
const { slug, title, description, ogImage, name } = Astro.props; const { slug, title, description, ogImage, name, productName, price, availability } = Astro.props;
const schemaProduct = {
"@context": "https://schema.org",
"@type": "Product",
"name": productName,
"description": description,
"image": ogImage,
"brand": { "@type": "Brand", "name": "REBOURS Studio" },
"url": `https://rebours.studio/collection/${slug}/`,
...(price ? {
"offers": {
"@type": "Offer",
"price": price,
"priceCurrency": "EUR",
"availability": availability,
"seller": { "@type": "Organization", "name": "REBOURS Studio" }
}
} : {}),
};
const schemaBreadcrumb = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Accueil", "item": "https://rebours.studio/" },
{ "@type": "ListItem", "position": 2, "name": "Collection 001", "item": "https://rebours.studio/#collection" },
{ "@type": "ListItem", "position": 3, "name": productName, "item": `https://rebours.studio/collection/${slug}/` },
]
};
--- ---
<Base <Base
title={title} title={title}
description={description} description={description}
ogImage={ogImage} ogImage={ogImage}
canonical={`https://rebour.studio/collection/${slug}`} canonical={`https://rebours.studio/collection/${slug}/`}
> >
<Fragment slot="head">
<script type="application/ld+json" set:html={JSON.stringify(schemaProduct)} />
<script type="application/ld+json" set:html={JSON.stringify(schemaBreadcrumb)} />
</Fragment>
<!-- On charge index.html entier et on ouvre le panel via JS au load --> <!-- On charge index.html entier et on ouvre le panel via JS au load -->
<meta name="x-open-panel" content={name} /> <meta name="x-open-panel" content={name} />

View File

@ -6,7 +6,7 @@ const schemaOrg = {
"@type": "Store", "@type": "Store",
"name": "REBOURS Studio", "name": "REBOURS Studio",
"description": "Mobilier d'art contemporain. Space Age × Memphis. Pièces uniques fabriquées à Paris.", "description": "Mobilier d'art contemporain. Space Age × Memphis. Pièces uniques fabriquées à Paris.",
"url": "https://rebours.studio", "url": "https://rebours.studio/",
"image": "https://rebours.studio/assets/lamp-violet.jpg", "image": "https://rebours.studio/assets/lamp-violet.jpg",
"address": { "@type": "PostalAddress", "addressLocality": "Paris", "addressCountry": "FR" }, "address": { "@type": "PostalAddress", "addressLocality": "Paris", "addressCountry": "FR" },
"hasOfferCatalog": { "hasOfferCatalog": {
@ -31,7 +31,7 @@ const schemaOrg = {
<Base <Base
title="REBOURS — Mobilier d'art contemporain | Collection 001" title="REBOURS — Mobilier d'art contemporain | Collection 001"
description="REBOUR Studio crée du mobilier d'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours." description="REBOUR Studio crée du mobilier d'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours."
canonical="https://rebour.studio/" canonical="https://rebours.studio/"
> >
<Fragment slot="head"> <Fragment slot="head">
<script type="application/ld+json" set:html={JSON.stringify(schemaOrg)} /> <script type="application/ld+json" set:html={JSON.stringify(schemaOrg)} />

View File

@ -3,10 +3,11 @@ import Base from '../layouts/Base.astro';
--- ---
<Base <Base
title="REBOUR — COMMANDE CONFIRMÉE" title="REBOURS — COMMANDE CONFIRMÉE"
description="Votre commande REBOURS Studio a été confirmée." description="Votre commande REBOURS Studio a été confirmée."
canonical="https://rebour.studio/success" canonical="https://rebours.studio/success"
> >
<meta name="robots" content="noindex, nofollow" slot="head">
<style> <style>
main { main {
flex-grow: 1; flex-grow: 1;
@ -91,7 +92,7 @@ import Base from '../layouts/Base.astro';
<main> <main>
<div class="left"> <div class="left">
<img id="product-img" class="product-img" src="/assets/lamp-violet.jpg" alt=""> <img id="product-img" class="product-img" src="/assets/lamp-violet.jpg" alt="Produit REBOURS Studio">
<p class="slabel" style="position:relative">// COMMANDE_CONFIRMÉE</p> <p class="slabel" style="position:relative">// COMMANDE_CONFIRMÉE</p>
<h1 style="position:relative">MERCI<br>POUR<br>VOTRE<br>COMMANDE</h1> <h1 style="position:relative">MERCI<br>POUR<br>VOTRE<br>COMMANDE</h1>
<p class="status-line" id="loading" style="position:relative">Vérification du paiement...</p> <p class="status-line" id="loading" style="position:relative">Vérification du paiement...</p>