feat: switch to SSR for live Sanity updates

Migrate from SSG to SSR with @astrojs/node adapter so Sanity CMS
changes are reflected immediately without rebuild. Separate ports
for Astro SSR (4321) and Fastify API (3000) in production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-04-04 11:41:34 +02:00
parent 7c77ac6c19
commit 1b53e04b5d
8 changed files with 191 additions and 59 deletions

View File

@ -37,7 +37,7 @@ rebours/
| Couche | Techno |
|--------|--------|
| Front (SSG) | Astro + HTML/CSS/JS vanilla + GSAP |
| Front (SSR) | Astro + HTML/CSS/JS vanilla + GSAP |
| CMS | Sanity (headless, hébergé) |
| API | Fastify (Node.js) |
| Paiement | Stripe Checkout (price_data inline) |
@ -65,20 +65,21 @@ STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=http://localhost:4321
PORT=8888
FASTIFY_PORT=3000 # Port Fastify API (prod)
ASTRO_PORT=4321 # Port Astro SSR (prod)
```
### Lancer le projet
```bash
npm install
npm run dev
pnpm install
pnpm dev
```
Cela lance en parallèle (via `concurrently`) :
- `astro dev` sur http://localhost:4321
- `node --watch server.mjs` (mode dev, PORT=8888)
- `node --watch server.mjs` (mode dev)
Le proxy Vite dans `astro.config.mjs` redirige `/api/*` vers `http://127.0.0.1:8888`.
Le proxy Vite dans `astro.config.mjs` redirige `/api/*` vers le serveur Fastify.
### Sanity Studio
```bash
@ -90,8 +91,8 @@ Accessible sur http://localhost:3333
### Build
```bash
npm run build
# Génère ./dist/ (fichiers statiques Astro)
pnpm build
# Génère ./dist/ (serveur Astro SSR + assets client)
```
---
@ -116,8 +117,7 @@ Champs principaux :
1. Ouvrir Sanity Studio
2. Créer un nouveau document "Produit"
3. Remplir les champs, uploader l'image
4. Publier
5. Rebuild le site : `npm run build` + déployer
4. Publier → visible immédiatement sur le site (SSR, pas de rebuild nécessaire)
### Images
Les images sont servies via le CDN Sanity avec transformations automatiques.
@ -154,12 +154,19 @@ Quand un client clique "Commander" :
### Serveur : ordinarthur@10.10.0.13
### Architecture prod
### Architecture prod (SSR)
```
Internet -> Nginx (port 80) -> /var/www/html/rebours/dist/ (statiques)
Internet -> Nginx (port 80) -> / -> proxy -> Astro SSR :4321
-> /_astro/* -> fichiers statiques (dist/client/)
-> /api/* -> proxy -> Fastify :3000
```
### Services systemd
| Service | Port | Rôle |
|---------|------|------|
| `rebours-ssr` | 4321 | Astro SSR (pages dynamiques) |
| `rebours` | 3000 | Fastify API (Stripe, checkout) |
### Variables d'environnement en prod
```env
SANITY_PROJECT_ID=...
@ -167,14 +174,15 @@ SANITY_DATASET=production
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=https://rebours.studio
PORT=3000
FASTIFY_PORT=3000
ASTRO_PORT=4321
```
### Déploiement
```bash
npm run build
pnpm build
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/"
ssh ordinarthur@10.10.0.13 "sudo rm -rf /var/www/html/rebours/dist && sudo mkdir -p /var/www/html/rebours/dist && sudo cp -r /tmp/rebours-dist/* /var/www/html/rebours/dist/ && sudo chown -R ordinarthur:ordinarthur /var/www/html/rebours/dist && sudo systemctl restart rebours-ssr"
```
Si server.mjs a changé :
@ -189,8 +197,8 @@ ssh ordinarthur@10.10.0.13 "sudo cp /tmp/server.mjs /var/www/html/rebours/server
| URL | Comportement |
|-----|-------------|
| `/` | Page principale Astro (SSG) |
| `/collection/{slug}` | Page produit (SSG), auto-open panel via `window.__OPEN_PANEL__` |
| `/` | Page principale Astro (SSR, données Sanity live) |
| `/collection/{slug}` | Page produit (SSR), auto-open panel via `window.__OPEN_PANEL__` |
| `/success?session_id=...` | Page de confirmation Stripe |
| `/robots.txt` | Généré au build |
| `/sitemap.xml` | Généré au build depuis Sanity |

View File

@ -1,8 +1,10 @@
// @ts-check
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'static',
output: 'server',
adapter: node({ mode: 'standalone' }),
outDir: './dist',
server: { port: 4321 },
vite: {

View File

@ -2,10 +2,7 @@ server {
listen 80;
server_name rebours.studio;
root /var/www/html/rebours/dist;
index index.html;
# ── API proxy Fastify ──────────────────────────────────────────────────
# ── API proxy Fastify ────────────────<EFBFBD><EFBFBD><EFBFBD>─────────────────────────────────
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
@ -14,28 +11,23 @@ server {
proxy_set_header X-Forwarded-Proto https;
}
# ── Cache : Astro hashed immutable ─────────────────────────────────────
# ── Static assets from Astro client build ──────────────────<EFBFBD><EFBFBD><EFBFBD>─────────────
location /_astro/ {
root /var/www/html/rebours/dist/client;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# ── Cache : CSS/JS sans hash revalidation ─────────────────────────────
location ~* \.(css|js)$ {
add_header Cache-Control "no-cache";
}
# ── Cache : assets 7 jours ────────────────────────────────────────────
location ~* \.(jpg|jpeg|png|gif|webp|svg|woff2|woff|ttf|ico|mp3)$ {
location ~* \.(css|js|jpg|jpeg|png|gif|webp|svg|woff2|woff|ttf|ico|mp3)$ {
root /var/www/html/rebours/dist/client;
add_header Cache-Control "public, max-age=604800";
}
# ── HTML : jamais caché ──────────────────────────────────────────────────
location ~* \.html$ {
add_header Cache-Control "no-store";
}
# ── SPA fallback ─────────────────────────────────────────────────────────
# ── SSR Astro Node server ─────────────────────<EFBFBD><EFBFBD><EFBFBD>────────────────────────
location / {
try_files $uri $uri/ $uri.html /index.html;
proxy_pass http://127.0.0.1:4321;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}

View File

@ -7,9 +7,11 @@
"build": "astro build",
"preview": "astro preview",
"server": "NODE_ENV=production node server.mjs",
"start": "NODE_ENV=production node dist/server/entry.mjs",
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^9.5.5",
"@fastify/cors": "^10.0.2",
"@sanity/client": "^7",
"@sanity/image-url": "^1",

133
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@astrojs/node':
specifier: ^9.5.5
version: 9.5.5(astro@5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3))
'@fastify/cors':
specifier: ^10.0.2
version: 10.1.0
@ -47,6 +50,11 @@ packages:
'@astrojs/markdown-remark@6.3.11':
resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==}
'@astrojs/node@9.5.5':
resolution: {integrity: sha512-rtU2BGU5u3SfGURpANfMxVzCIoR86MkaN05ncza9rbtuMKJ/XnRJt/BbyVknDbOJ71hoci0SIsJwKcJR8vvi/A==}
peerDependencies:
astro: ^5.17.3
'@astrojs/prism@3.3.0':
resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==}
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0}
@ -989,6 +997,10 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@ -1038,12 +1050,19 @@ packages:
resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
engines: {node: '>=4'}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@ -1069,6 +1088,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
@ -1079,6 +1101,10 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
event-source-polyfill@1.0.31:
resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
@ -1149,6 +1175,10 @@ packages:
resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==}
engines: {node: '>=20'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -1218,6 +1248,10 @@ packages:
http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
import-meta-resolve@4.2.0:
resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
@ -1423,6 +1457,14 @@ packages:
micromark@4.0.2:
resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@ -1475,6 +1517,10 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
@ -1553,6 +1599,10 @@ packages:
radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@ -1663,9 +1713,19 @@ packages:
engines: {node: '>=10'}
hasBin: true
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
server-destroy@1.0.1:
resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -1698,6 +1758,10 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@ -1764,6 +1828,10 @@ packages:
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
engines: {node: '>=12'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@ -2061,6 +2129,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@astrojs/node@9.5.5(astro@5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3))':
dependencies:
'@astrojs/internal-helpers': 0.7.6
astro: 5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3)
send: 1.2.1
server-destroy: 1.0.1
transitivePeerDependencies:
- supports-color
'@astrojs/prism@3.3.0':
dependencies:
prismjs: 1.30.0
@ -2826,6 +2903,8 @@ snapshots:
defu@6.1.4: {}
depd@2.0.0: {}
dequal@2.0.3: {}
destr@2.0.5: {}
@ -2869,10 +2948,14 @@ snapshots:
dset@3.1.4: {}
ee-first@1.1.1: {}
emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {}
encodeurl@2.0.0: {}
entities@4.5.0: {}
entities@6.0.1: {}
@ -2939,6 +3022,8 @@ snapshots:
escalade@3.2.0: {}
escape-html@1.0.3: {}
escape-string-regexp@5.0.0: {}
estree-walker@2.0.2: {}
@ -2947,6 +3032,8 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
etag@1.8.1: {}
event-source-polyfill@1.0.31: {}
eventemitter3@5.0.4: {}
@ -3020,6 +3107,8 @@ snapshots:
dependencies:
tiny-inflate: 1.0.3
fresh@2.0.0: {}
fsevents@2.3.3:
optional: true
@ -3149,6 +3238,14 @@ snapshots:
http-cache-semantics@4.2.0: {}
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
import-meta-resolve@4.2.0: {}
inherits@2.0.4: {}
@ -3525,6 +3622,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
mime-db@1.54.0: {}
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
mimic-response@3.1.0: {}
mnemonist@0.40.0:
@ -3565,6 +3668,10 @@ snapshots:
on-exit-leak-free@2.1.2: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
oniguruma-parser@0.12.1: {}
oniguruma-to-es@4.3.4:
@ -3650,6 +3757,8 @@ snapshots:
radix3@1.1.2: {}
range-parser@1.2.1: {}
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@ -3820,8 +3929,28 @@ snapshots:
semver@7.7.4: {}
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
server-destroy@1.0.1: {}
set-cookie-parser@2.7.2: {}
setprototypeof@1.2.0: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.1.0
@ -3881,6 +4010,8 @@ snapshots:
split2@4.2.0: {}
statuses@2.0.2: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@ -3951,6 +4082,8 @@ snapshots:
toad-cache@3.7.0: {}
toidentifier@1.0.1: {}
tree-kill@1.2.2: {}
trim-lines@3.0.1: {}

View File

@ -131,7 +131,7 @@ app.get('/api/session/:id', async (request) => {
// ── Start ───────────────────────────────────────────────────────────────────
try {
await app.listen({ port: process.env.PORT ?? 3000, host: '0.0.0.0' })
await app.listen({ port: process.env.FASTIFY_PORT ?? process.env.PORT ?? 3000, host: '0.0.0.0' })
} catch (err) {
app.log.error(err)
process.exit(1)

View File

@ -5,7 +5,7 @@ export const sanity = createClient({
projectId: import.meta.env.SANITY_PROJECT_ID,
dataset: import.meta.env.SANITY_DATASET || 'production',
apiVersion: '2024-01-01',
useCdn: true,
useCdn: false,
})
const builder = imageUrlBuilder(sanity)

View File

@ -1,26 +1,21 @@
---
import Base from '../../layouts/Base.astro';
import { getPublishedProducts, urlFor } from '../../lib/sanity.mjs';
import { getPublishedProducts, getProductBySlug, urlFor } from '../../lib/sanity.mjs';
export async function getStaticPaths() {
const products = await getPublishedProducts();
const { slug } = Astro.params;
const product = await getProductBySlug(slug);
return products.map(p => ({
params: { slug: p.slug },
props: {
slug: p.slug,
name: p.name,
title: p.seoTitle || `REBOURS — ${p.productDisplayName} | Collection 001`,
description: p.seoDescription || p.description?.substring(0, 155) || '',
ogImage: p.image ? urlFor(p.image).width(1200).url() : '',
productName: p.productDisplayName,
price: p.price ? String(p.price / 100) : null,
availability: p.availability || 'https://schema.org/PreOrder',
},
}));
if (!product) {
return Astro.redirect('/');
}
const { slug, title, description, ogImage, name, productName, price, availability } = Astro.props;
const name = product.name;
const title = product.seoTitle || `REBOURS — ${product.productDisplayName} | Collection 001`;
const description = product.seoDescription || product.description?.substring(0, 155) || '';
const ogImage = product.image ? urlFor(product.image).width(1200).url() : '';
const productName = product.productDisplayName;
const price = product.price ? String(product.price / 100) : null;
const availability = product.availability || 'https://schema.org/PreOrder';
const allProducts = await getPublishedProducts();
@ -109,11 +104,11 @@ const schemaBreadcrumb = {
<p id="panel-desc" class="panel-desc"></p>
<hr>
<details class="accordion">
<details class="accordion" open>
<summary>SPÉCIFICATIONS TECHNIQUES <span>↓</span></summary>
<div class="accordion-body" id="panel-specs"></div>
</details>
<details class="accordion">
<details class="accordion" open>
<summary>NOTES DE CONCEPTION <span>↓</span></summary>
<div class="accordion-body" id="panel-notes"></div>
</details>