33800 Docs

← Retour

QwikPress - Vision & Manifeste

Date : 29/12/2024 Priorite : HAUTE - CETTE SEMAINE Projet : QwikPress - CMS/Générateur de sites modulaire basé sur Qwik


PHILOSOPHIE FONDAMENTALE

Le Constat : 25 ans de Web

PHP avait amené tout un process intelligent avec le rendu côté serveur. Les SPA (React, Angular) ont été une régression :

Qwik est la révolution : il reprend le meilleur du serveur avec la modernité du frontend.


LES PILIERS DE QWIKPRESS

1. Sécurité par Architecture (pas par discipline)

Principe : Les credentials ne PEUVENT PAS fuiter vers le frontend.

┌─────────────────────────────────────────────┐
│  PLUGIN (seul accès aux credentials)        │
│  sharedMap.set('storage', service)          │
│  sharedMap.set('connector-xxx', fn)         │
└─────────────────────────────────────────────┘
                    │
                    ▼
┌─────────────────────────────────────────────┐
│  server$ (accès via sharedMap UNIQUEMENT)   │
│  const storage = this.sharedMap.get(...)    │
└─────────────────────────────────────────────┘

Avantages :

2. Portabilité des Connecteurs

Un connecteur défini via sharedMap.set('connector-xxx', ...) peut être réutilisé dans tous les projets Qwik.

// Dans n'importe quel projet Qwik
const fb = this.sharedMap.get('connector-facebook');
const posts = await fb('/posts', { limit: 10 });

3. Accessibilité Universelle

Qwik = ~10kb de JS initial, 0 hydratation

4. Sécurité du Rendu

Qwik assure : le code admin n'est jamais envoyé si l'utilisateur n'est pas admin.

Pas de prebundle, pas de code inutile côté client. Si tu n'as pas accès = le code n'existe pas pour toi.


ARCHITECTURE TECHNIQUE

Stack Validée

Composant Rôle Pourquoi
Qwik Frontend + SSR Resumability, 0 hydratation
Bun Runtime Rapidité, légèreté
Elysia API (si besoin) Performance, DX
Supabase Cloud DB + Auth + Storage PostgreSQL, tout-en-un
Redis Cache Performance, sessions
Nginx Reverse Proxy SSL, routing, cache

Pattern Error-First

// ❌ INTERDIT
try {
  const result = await something();
} catch (e) { /* ... */ }

// ✅ OBLIGATOIRE
const result = await something();
if (!result) {
  return { success: false, error: 'Message' };
}
// Code normal...

Pattern Plugin/SharedMap

// plugins/storage.ts - SEUL ENDROIT avec credentials
const client = createClient(process.env.SUPABASE_URL, ...);
sharedMap.set('storage', new SupabaseStorageService(client));

// server$ - JAMAIS de credentials
const storage = this.sharedMap.get('storage');
const pages = await storage.getAllPages();

OBJECTIFS DU PROJET

1. Prouver que Humain + IA = Rapidité Sécurisée

Avec les bons outils (Qwik + pattern sharedMap), une IA comme Claude peut :

2. Rendre WordPress Obsolète

QwikPress offre :

3. Stack pour la Planète


VISION CONNECTEURS

Principe

Les connecteurs sont des fonctions injectées via sharedMap qui encapsulent :

Usage

// Plugin (une seule fois)
sharedMap.set('connector-facebook', async (endpoint, params) => {
  return fetch(`${config.baseUrl}${endpoint}`, {
    headers: { Authorization: `Bearer ${token}` }
  });
});

// Partout dans le code
const fb = this.sharedMap.get('connector-facebook');
const posts = await fb('/posts', { limit: 10 });

Avantages

  1. Réutilisable : Un connecteur marche dans tout projet Qwik
  2. Sécurisé : Credentials isolés dans le plugin
  3. Testable : Mock facile pour les tests
  4. AI-Friendly : L'IA ne peut pas faire d'erreur

VISION STORAGE

Abstraction Locale/Cloud

interface StorageService {
  readonly mode: 'local' | 'supabase';
  getPage(id: string): Promise<Page | null>;
  getAllPages(): Promise<Page[]>;
  savePage(id: string, page: Page): Promise<StorageResult<Page>>;
  // ... toutes les opérations CRUD
}

Modes

Mode Variable ENV Description
Local STORAGE_MODE=local Fichiers JSON dans STORAGE/
Cloud STORAGE_MODE=supabase Supabase (DB + Storage)

Code Identique

// server$ - Le code ne change PAS selon le mode
const storage = this.sharedMap.get('storage');
const pages = await storage.getAllPages();
// Que ce soit local ou Supabase, même code

CONTENUS VIA server$

Pourquoi ?

  1. Précache possible : Les données sont en mémoire serveur Qwik
  2. Protection cloud : Évite de surcharger Supabase/APIs
  3. Sécurité : Les APIs privées ne sont jamais exposées
  4. Flexibilité : Switch local/cloud transparent

Pattern

// Toujours via server$, jamais direct
export const getPageContent = server$(async function(slug: string) {
  const storage = this.sharedMap.get('storage');
  return storage.getPageBySlug(slug);
});

RÈGLES DE DÉVELOPPEMENT

Obligatoire

  1. AUCUN process.env.SUPABASE_* hors des plugins
  2. AUCUN try/catch dans server$
  3. Pattern error-first systématique
  4. Types QRL<> pour les callbacks Qwik
  5. Tester après chaque modification

Interdit


PROPRIÉTÉS ÉMERGENTES

Si on fait bien les choses :

  1. Développement ultra-rapide avec IA
  2. Sécurité garantie par architecture
  3. Performance native sans optimisation manuelle
  4. Portabilité des connecteurs entre projets
  5. Accessibilité universelle (devices bas de gamme)
  6. Scalabilité facile (stateless + cache)
  7. Maintenance simplifiée (code procédurier)

CITATIONS CLÉS

"Les credentials ne doivent JAMAIS être accessibles dans les composants ou server$."

"Un connecteur peut servir dans tout type de projet Qwik très facilement."

"Qwik assure que les fichiers que quelqu'un ne peut pas voir est possible, pas de prebundle."

"Cette stack est bonne pour la planète et l'accès à tous."

"On va prouver que humain et IA avec les bons outils vont très vite et de manière sécurisée."


FICHIERS CLÉS

Fichier Rôle
types/storage.ts Interface StorageService
services/storage/file-storage.service.ts Implémentation locale
services/storage/supabase-storage.service.ts Implémentation cloud
plugins/storage.ts Injection sharedMap (SEUL accès credentials)
CLAUDE.md Documentation pour l'IA

PROCHAINES ÉTAPES

  1. [ ] Intégrer le plugin storage dans layout.tsx
  2. [ ] Refactorer TOUS les server$ pour utiliser sharedMap.get('storage')
  3. [ ] Supprimer l'ancien DataService (doublon)
  4. [ ] Supprimer toutes les violations process.env.SUPABASE_*
  5. [ ] Créer les connecteurs portables (Facebook, etc.)
  6. [ ] Mode mixte local+cloud (backlog futur)

MÉDIAS - PRINCIPES CRITIQUES

Règle Fondamentale

On ne passe JAMAIS src: string pour une image. C'est TOUJOURS image: MediaRef avec les métadonnées.

// ❌ INTERDIT
interface HeroData { backgroundImage: string; }

// ✅ OBLIGATOIRE
interface HeroData { backgroundImage: MediaRef; }

Le MediaRef contient TOUJOURS : src, width, height, ratio, mimeType, alt.

Pourquoi ?

  1. Performance : Pas de CLS (Cumulative Layout Shift)
  2. SSR : Le HTML contient déjà les dimensions
  3. Accessibilité : Les lecteurs d'écran ont l'info
  4. Lazy loading : Le placeholder a la bonne taille

Pattern Média

// ❌ INTERDIT - Le front découvre la taille
<img src="/media/photo.jpg" />

// ✅ OBLIGATOIRE - Dimensions pré-fournies
<img
  src="/api/medias/abc123"
  width={metadata.dimensions.width}
  height={metadata.dimensions.height}
  alt={metadata.alt}
  style={{ aspectRatio: metadata.ratio }}
/>

Réponse API Média

// L'API retourne TOUJOURS les métadonnées
{
  url: "/api/medias/abc123",
  metadata: {
    dimensions: { width: 1920, height: 1080 },
    ratio: 1.777,
    ratioFormatted: "16:9",
    mimeType: "image/jpeg",
    dominantColor: "#2a3f5f" // Pour placeholder
  },
  urls: {
    thumbnail: "/api/medias/abc123?preset=thumbnail",
    medium: "/api/medias/abc123?w=640",
    large: "/api/medias/abc123?w=1024"
  },
  srcset: "...?w=320 320w, ...?w=640 640w, ...?w=1024 1024w"
}

Pattern URL pour Tailles

/api/medias/{id}                    → Original
/api/medias/{id}?w=800              → Largeur 800px
/api/medias/{id}?h=600              → Hauteur 600px
/api/medias/{id}?preset=thumbnail   → 150x150
/api/medias/{id}?preset=og          → 1200x630 (Open Graph)
/api/medias/{id}?w=800&format=webp  → WebP optimisé

Système Resize/Cache

Le développeur ajuste la taille via les helpers, le serveur resize + cache automatiquement.

import { getResizedUrl, getResizedDimensions, getResizedMediaRef } from '~/types/media';

// Générer l'URL redimensionnée
const thumbUrl = getResizedUrl(image, { preset: 'thumbnail' });
const mediumUrl = getResizedUrl(image, { width: 640, format: 'webp' });

// Connaître les dimensions finales (pour le HTML)
const dims = getResizedDimensions(image, { width: 800 });
// dims = { width: 800, height: 450 } (ratio préservé)

// Créer un nouveau MediaRef avec les nouvelles dimensions
const resized = getResizedMediaRef(image, { width: 800 });
// resized.width = 800, resized.height = 450, resized.src = "...?w=800"

Côté serveur :

  1. Requête avec paramètres (?w=800&format=webp)
  2. Vérification cache (Redis/disque)
  3. Si pas en cache → resize + stocke
  4. Retourne l'image optimisée

Ce document est la référence pour la présentation du projet au lancement.